kern 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +13 -2
  4. data/README.md +15 -13
  5. data/Rakefile +3 -3
  6. data/app/assets/builds/tailwind.kern.css +1749 -0
  7. data/app/controllers/kern/billing_profiles/subscriptions_controller.rb +37 -0
  8. data/app/controllers/kern/billing_profiles/subscriptions_controller.rb.tt +37 -0
  9. data/app/controllers/kern/settings/subscriptions_controller.rb +6 -0
  10. data/app/controllers/kern/signups_controller.rb +1 -1
  11. data/app/models/billing_profile/subscription.rb +16 -0
  12. data/app/models/billing_profile.rb +7 -0
  13. data/app/models/session.rb +1 -1
  14. data/app/models/signup.rb +1 -1
  15. data/app/models/user.rb +1 -1
  16. data/app/models/workspace/access.rb +3 -0
  17. data/app/models/workspace/access.rb.tt +10 -0
  18. data/app/models/workspace/billable.rb +16 -0
  19. data/app/models/workspace/feature_access.rb +28 -0
  20. data/app/models/workspace/members.rb +9 -7
  21. data/app/models/workspace/setup.rb +5 -3
  22. data/app/models/workspace.rb +2 -0
  23. data/app/views/components/_dialog.html.erb +36 -0
  24. data/app/views/kern/pages/welcome.html.erb +10 -6
  25. data/app/views/kern/settings/_cards.html.erb +11 -3
  26. data/app/views/kern/settings/subscriptions/_plan.html.erb +35 -0
  27. data/app/views/kern/settings/subscriptions/show.html.erb +11 -0
  28. data/app/views/kern/settings/users/show.html.erb +2 -2
  29. data/app/views/kern/signups/new.html.erb +2 -0
  30. data/app/views/layouts/kern/application.html.erb +4 -2
  31. data/app/views/layouts/kern/application.html.erb.tt +2 -1
  32. data/app/views/layouts/kern/auth.html.erb +2 -2
  33. data/app/webhooks/stripe/base.rb +31 -0
  34. data/app/webhooks/stripe/checkout_session_completed.rb +46 -0
  35. data/app/webhooks/stripe/customer_subscription_deleted.rb +20 -0
  36. data/app/webhooks/stripe/customer_subscription_updated.rb +33 -0
  37. data/bin/release +2 -3
  38. data/config/initializers/stripe.rb +5 -0
  39. data/config/initializers/stripe.rb.tt +3 -0
  40. data/config/routes.rb +4 -0
  41. data/db/migrate/20250101000007_create_billing_profiles.rb +14 -0
  42. data/db/migrate/20250101000008_create_billing_profile_subscriptions.rb +20 -0
  43. data/lib/generators/kern/feature/USAGE +1 -0
  44. data/lib/generators/kern/feature/feature_generator.rb +82 -19
  45. data/lib/generators/kern/install/install_generator.rb +43 -7
  46. data/lib/generators/kern/install/templates/POST_INSTALL +25 -0
  47. data/lib/generators/kern/install/templates/configurations/plans.yml +43 -0
  48. data/lib/generators/kern/install/templates/configurations/stripe.yml +11 -0
  49. data/lib/generators/kern/layouts/layouts_generator.rb +12 -0
  50. data/lib/kern/version.rb +1 -1
  51. metadata +27 -5
  52. data/app/assets/builds/tailwind.css +0 -2
  53. data/lib/generators/kern/views/USAGE +0 -22
  54. data/lib/generators/kern/views/views_generator.rb +0 -42
@@ -0,0 +1,14 @@
1
+ class CreateBillingProfiles < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :billing_profiles do |t|
4
+ t.belongs_to :workspace, null: false, foreign_key: true
5
+ t.string :slug, null: true
6
+ t.string :external_id, null: true # This would be payment provider's (Stripe's) customer_id
7
+
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :billing_profiles, [:slug, :workspace_id], unique: true
12
+ add_index :billing_profiles, :external_id
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ class CreateBillingProfileSubscriptions < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :billing_profile_subscriptions do |t|
4
+ t.belongs_to :billing_profile, null: false, foreign_key: true
5
+ t.string :slug, null: false
6
+ t.string :subscription_id, null: false
7
+ t.string :subscription_item_id, null: false
8
+ t.string :status, null: false
9
+ t.datetime :current_period_end_at, null: true
10
+ t.datetime :cancel_at, null: true
11
+ t.json :metadata, null: false, default: {}
12
+
13
+ t.timestamps
14
+ end
15
+
16
+ add_index :billing_profile_subscriptions, [:slug, :billing_profile_id], unique: true
17
+ add_index :billing_profile_subscriptions, :subscription_id
18
+ add_index :billing_profile_subscriptions, :subscription_item_id
19
+ end
20
+ end
@@ -11,6 +11,7 @@ Example:
11
11
  This will generate sessions and passwords features.
12
12
 
13
13
  Available features:
14
+ billing Billing and subscription functionality
14
15
  passwords Password reset functionality
15
16
  sessions User authentication (sign in/out)
16
17
  settings User settings (user, workspace settings and subscription/billing)
@@ -2,23 +2,27 @@ module Kern
2
2
  class FeatureGenerator < Rails::Generators::Base
3
3
  source_root File.expand_path("../../../../../", __FILE__)
4
4
 
5
- AVAILABLE_FEATURES = %w[passwords sessions settings signups]
5
+ AVAILABLE_FEATURES = %w[billing passwords sessions settings signups]
6
6
 
7
7
  argument :features, type: :array, required: true, banner: "feature feature"
8
8
 
9
+ class_option :skip_encryption, type: :boolean, default: false, desc: "Skip encryption for sensitive fields"
10
+
9
11
  def copy_features
10
12
  features.each do |feature|
11
13
  verify_exists!(feature)
12
14
 
13
15
  case feature
14
- when "sessions"
15
- generate_sessions
16
- when "signups"
17
- generate_signups
16
+ when "billing"
17
+ generate_billing
18
18
  when "passwords"
19
19
  generate_passwords
20
+ when "sessions"
21
+ generate_sessions
20
22
  when "settings"
21
23
  generate_settings
24
+ when "signups"
25
+ generate_signups
22
26
  end
23
27
  end
24
28
  end
@@ -33,39 +37,61 @@ module Kern
33
37
  exit(1)
34
38
  end
35
39
 
36
- def generate_sessions
37
- directory "app/models", "app/models"
38
- template "app/controllers/concerns/authentication.rb.tt", "app/controllers/concerns/authentication.rb"
39
- copy_file "app/controllers/kern/sessions_controller.rb", "app/controllers/sessions_controller.rb"
40
- directory "app/views/kern/sessions", "app/views/sessions"
40
+ def generate_billing
41
+ template "config/initializers/stripe.rb.tt", "config/initializers/stripe.rb"
41
42
 
42
- route "resource :session, only: %w[new create destroy]"
43
- end
43
+ [
44
+ "billing_profile.rb",
45
+ "billing_profile/subscription.rb",
46
+ "workspace/billable.rb"
47
+ ].each { copy_file "app/models/#{it}", "app/models/#{it}" }
44
48
 
45
- def generate_signups
46
- directory "app/models", "app/models" unless features.include?("sessions")
49
+ inject_into_file "app/models/workspace.rb", " include Billable\n\n", after: "include Sluggable\n"
47
50
 
48
- template "app/controllers/kern/signups_controller.rb.tt", "app/controllers/signups_controller.rb"
49
- directory "app/views/kern/signups", "app/views/signups"
51
+ route "resource :subscriptions, module: :billing_profiles, only: %w[create edit]"
52
+ inject_into_file "config/routes.rb", " resource :subscriptions, only: %w[show]\n", after: "namespace :settings do\n"
50
53
 
51
- route "resource :signup, only: %w[new create]"
54
+ copy_file "app/views/kern/settings/subscriptions/show.html.erb", "app/views/settings/subscriptions/show.html.erb"
55
+ copy_file "app/views/kern/settings/subscriptions/_plan.html.erb", "app/views/settings/subscriptions/_plan.html.erb"
56
+
57
+ copy_file "app/controllers/kern/settings/subscriptions_controller.rb", "app/controllers/settings/subscriptions_controller.rb"
58
+ template "app/controllers/kern/billing_profiles/subscriptions_controller.rb.tt", "app/controllers/billing_profiles/subscriptions_controller.rb"
52
59
  end
53
60
 
54
61
  def generate_passwords
55
- directory "app/views/kern/passwords", "app/views/passwords"
56
62
  copy_file "app/controllers/kern/passwords_controller.rb", "app/controllers/passwords_controller.rb"
57
63
  copy_file "app/mailers/kern/passwords_mailer.rb", "app/mailers/passwords_mailer.rb"
64
+
58
65
  template "app/views/kern/passwords_mailer/reset.text.erb.tt", "app/views/passwords_mailer/reset.text.erb"
59
66
  template "app/views/kern/passwords_mailer/reset.html.erb.tt", "app/views/passwords_mailer/reset.html.erb"
60
67
 
68
+ copy_file "app/views/kern/passwords/new.html.erb", "app/views/passwords/new.html.erb"
69
+ copy_file "app/views/kern/passwords/edit.html.erb", "app/views/passwords/edit.html.erb"
70
+
61
71
  route "resources :passwords, param: :token, only: %w[new create edit update]"
62
72
  end
63
73
 
74
+ def generate_sessions
75
+ AUTHENTICATION_MODELS.each { copy_file "app/models/#{it}", "app/models/#{it}" }
76
+
77
+ template "app/controllers/concerns/authentication.rb.tt", "app/controllers/concerns/authentication.rb"
78
+ copy_file "app/controllers/kern/sessions_controller.rb", "app/controllers/sessions_controller.rb"
79
+
80
+ copy_file "app/views/kern/sessions/new.html.erb", "app/views/sessions/new.html.erb"
81
+
82
+ remove_encryption_from_models
83
+
84
+ route "resource :session, only: %w[new create destroy]"
85
+ end
86
+
64
87
  def generate_settings
65
- directory "app/views/kern/settings", "app/views/settings"
66
88
  copy_file "app/controllers/kern/settings_controller.rb", "app/controllers/settings_controller.rb"
67
89
  directory "app/controllers/kern/settings", "app/controllers/settings"
68
90
 
91
+ copy_file "app/views/kern/settings/show.html.erb", "app/views/settings/show.html.erb"
92
+ copy_file "app/views/kern/settings/_cards.html.erb", "app/views/settings/_cards.html.erb"
93
+ copy_file "app/views/kern/settings/users/show.html.erb", "app/views/settings/users/show.html.erb"
94
+
69
95
  route <<~RUBY
70
96
  resource :settings, only: %w[show]
71
97
  namespace :settings do
@@ -73,5 +99,42 @@ module Kern
73
99
  end
74
100
  RUBY
75
101
  end
102
+
103
+ def generate_signups
104
+ AUTHENTICATION_MODELS.each { copy_file "app/models/#{it}", "app/models/#{it}" } unless features.include?("sessions")
105
+
106
+ template "app/controllers/kern/signups_controller.rb.tt", "app/controllers/signups_controller.rb"
107
+
108
+ copy_file "app/views/kern/signups/new.html.erb", "app/views/signups/new.html.erb"
109
+
110
+ remove_encryption_from_models
111
+
112
+ route "resource :signup, only: %w[new create]"
113
+ end
114
+
115
+ def remove_encryption_from_models
116
+ return unless options[:skip_encryption]
117
+
118
+ gsub_file "app/models/session.rb", /\n encrypts :user_agent, :ip\n/, ""
119
+ gsub_file "app/models/user.rb", /\n encrypts :email, deterministic: true, downcase: true\n/, ""
120
+ end
121
+
122
+ AUTHENTICATION_MODELS = [
123
+ "actor.rb",
124
+ "application_form.rb",
125
+ "current.rb",
126
+ "member.rb",
127
+ "member/acting.rb",
128
+ "member/setup.rb",
129
+ "role.rb",
130
+ "session.rb",
131
+ "signup.rb",
132
+ "user.rb",
133
+ "user/workspace_member.rb",
134
+ "workspace.rb",
135
+ "workspace/feature_access.rb",
136
+ "workspace/members.rb",
137
+ "workspace/setup.rb"
138
+ ]
76
139
  end
77
140
  end
@@ -8,6 +8,23 @@ module Kern
8
8
 
9
9
  class_option :skip_migrations, type: :boolean, default: false
10
10
 
11
+ def add_gems
12
+ gem "fuik"
13
+ gem "rails_icons"
14
+ gem "rails_vault"
15
+ gem "stripe"
16
+ end
17
+
18
+ def enable_bcrypt
19
+ if File.read(File.expand_path("Gemfile", destination_root)).include?('gem "bcrypt"')
20
+ uncomment_lines "Gemfile", /gem "bcrypt"/
21
+
22
+ bundle_command("install --quiet")
23
+ else
24
+ bundle_command("add bcrypt", {}, quiet: true)
25
+ end
26
+ end
27
+
11
28
  def copy_migrations
12
29
  return if options[:skip_migrations]
13
30
 
@@ -26,23 +43,42 @@ module Kern
26
43
  end
27
44
  end
28
45
 
29
- def copy_urls_configuration
46
+ def copy_configurations
30
47
  template "configurations/urls.yml", "config/configurations/urls.yml"
48
+ template "configurations/stripe.yml", "config/configurations/stripe.yml"
49
+ template "configurations/plans.yml", "config/configurations/plans.yml"
50
+
31
51
  template "configurations/README.md", "config/configurations/README.md"
32
52
  end
33
53
 
34
- def enable_bcrypt
35
- if File.read(File.expand_path("Gemfile", destination_root)).include?('gem "bcrypt"')
36
- uncomment_lines "Gemfile", /gem "bcrypt"/
54
+ def setup_workspace_access
55
+ Bundler.with_unbundled_env do
56
+ rails_command "generate rails_vault:install"
57
+ end
37
58
 
38
- bundle_command("install --quiet")
39
- else
40
- bundle_command("add bcrypt", {}, quiet: true)
59
+ source_paths.unshift(File.expand_path("../../../..", __dir__))
60
+ template "app/models/workspace/access.rb.tt", "app/models/workspace/access.rb"
61
+ source_paths.shift
62
+ end
63
+
64
+ def install_fuik
65
+ Bundler.with_unbundled_env do
66
+ rails_command "generate fuik:install"
41
67
  end
68
+
69
+ route "mount Fuik::Engine, at: '/'" if Rails.env.test? # ¯\_(ツ)_/¯
70
+
71
+ source_paths.unshift(File.expand_path("../../../..", __dir__))
72
+ directory "app/webhooks/stripe", "app/webhooks/stripe"
73
+ source_paths.shift
42
74
  end
43
75
 
44
76
  def mount_engine
45
77
  route 'mount Kern::Engine => "/"'
46
78
  end
79
+
80
+ def post_install_message
81
+ readme "POST_INSTALL" if behavior == :invoke
82
+ end
47
83
  end
48
84
  end
@@ -0,0 +1,25 @@
1
+
2
+ Kern is ready. One quick setup step.
3
+
4
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
5
+
6
+ REQUIRED SETUP
7
+
8
+ Encryption keys
9
+
10
+ Kern uses Active Record Encryption to encrypt sensitive user data
11
+ (email address, session IP and user agent). Run:
12
+
13
+ $ bin/rails db:encryption:init
14
+
15
+ This will output the required encryption keys you can add to your credential
16
+ files (bin/rails credentials:edit --environment=development).
17
+
18
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
19
+
20
+ Next steps:
21
+
22
+ 1. Run migrations: bin/rails db:migrate
23
+ 2. Start your server: bin/dev
24
+ 3. Check out the full documentation at https://saas.railsdesigner.com/
25
+
@@ -0,0 +1,43 @@
1
+ shared:
2
+ default:
3
+ name: default
4
+ price_id: price_id
5
+ amount: 0
6
+ features:
7
+ member_count: 1
8
+ email_notifications: true
9
+
10
+ production:
11
+ prod_id_1:
12
+ name: lite
13
+ price_id: price_1sv3l7ih6mxtl9cdmk5nqkrh
14
+ amount: 49
15
+ features:
16
+ member_count: 1
17
+ email_notifications: false
18
+
19
+ prod_id_2:
20
+ name: starter
21
+ price_id: price_id
22
+ amount: 99
23
+ features:
24
+ member_count: 10
25
+ email_notifications: true
26
+
27
+ development:
28
+ pro_plan_id:
29
+ name: pro
30
+ price_id: price_id
31
+ amount: 49
32
+ features:
33
+ member_count: 1
34
+ email_notifications: true
35
+
36
+ test:
37
+ pro_plan_id:
38
+ name: pro
39
+ price_id: price_id
40
+ amount: 49
41
+ features:
42
+ member_count: 1
43
+ email_notifications: true
@@ -0,0 +1,11 @@
1
+ shared:
2
+ api_key: <%= ENV.fetch("STRIPE_API_KEY", Rails.application.credentials&.stripe&.api_key) %>
3
+ api_version: <%= ENV.fetch("STRIPE_API_VERSION", "2025-10-29.clover") %>
4
+ max_network_retries: <%= ENV.fetch("STRIPE_MAX_NETWORK_RETRIES", 2) %>
5
+
6
+ development:
7
+ default_price_id: default_price_id
8
+
9
+ production:
10
+ default_price_id: <%= ENV.fetch("STRIPE_DEFAULT_PRICE_ID", "price_1SV3laIh6mxtl9CDpVY0viQy") %>
11
+ signing_secret_key: <%= ENV.fetch("STRIPE_SIGNING_KEY", Rails.application.credentials&.stripe&.signing_secret) %>
@@ -0,0 +1,12 @@
1
+ module Kern
2
+ class LayoutsGenerator < Rails::Generators::Base
3
+ source_root File.expand_path("../../../../../app/views", __FILE__)
4
+
5
+ def copy_layouts
6
+ template "layouts/kern/application.html.erb.tt", "app/views/layouts/application.html.erb"
7
+ copy_file "layouts/kern/application/_navigation.html.erb", "app/views/layouts/application/_navigation.html.erb"
8
+
9
+ copy_file "layouts/kern/auth.html.erb", "app/views/layouts/auth.html.erb"
10
+ end
11
+ end
12
+ end
data/lib/kern/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kern
2
- VERSION = "0.5.0"
2
+ VERSION = "0.7.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kern
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rails Designer
@@ -35,13 +35,16 @@ files:
35
35
  - Gemfile.lock
36
36
  - README.md
37
37
  - Rakefile
38
- - app/assets/builds/tailwind.css
38
+ - app/assets/builds/tailwind.kern.css
39
39
  - app/controllers/concerns/authentication.rb
40
40
  - app/controllers/concerns/authentication.rb.tt
41
41
  - app/controllers/kern/application_controller.rb
42
+ - app/controllers/kern/billing_profiles/subscriptions_controller.rb
43
+ - app/controllers/kern/billing_profiles/subscriptions_controller.rb.tt
42
44
  - app/controllers/kern/pages_controller.rb
43
45
  - app/controllers/kern/passwords_controller.rb
44
46
  - app/controllers/kern/sessions_controller.rb
47
+ - app/controllers/kern/settings/subscriptions_controller.rb
45
48
  - app/controllers/kern/settings/users_controller.rb
46
49
  - app/controllers/kern/settings_controller.rb
47
50
  - app/controllers/kern/signups_controller.rb
@@ -52,6 +55,8 @@ files:
52
55
  - app/mailers/kern/passwords_mailer.rb
53
56
  - app/models/actor.rb
54
57
  - app/models/application_form.rb
58
+ - app/models/billing_profile.rb
59
+ - app/models/billing_profile/subscription.rb
55
60
  - app/models/current.rb
56
61
  - app/models/member.rb
57
62
  - app/models/member/acting.rb
@@ -62,9 +67,14 @@ files:
62
67
  - app/models/user.rb
63
68
  - app/models/user/workspace_member.rb
64
69
  - app/models/workspace.rb
70
+ - app/models/workspace/access.rb
71
+ - app/models/workspace/access.rb.tt
72
+ - app/models/workspace/billable.rb
73
+ - app/models/workspace/feature_access.rb
65
74
  - app/models/workspace/members.rb
66
75
  - app/models/workspace/setup.rb
67
76
  - app/views/components/_container.html.erb
77
+ - app/views/components/_dialog.html.erb
68
78
  - app/views/components/_flash.html.erb
69
79
  - app/views/components/_heading.html.erb
70
80
  - app/views/components/flash/_message.html.erb
@@ -78,15 +88,23 @@ files:
78
88
  - app/views/kern/sessions/new.html.erb
79
89
  - app/views/kern/settings/_cards.html.erb
80
90
  - app/views/kern/settings/show.html.erb
91
+ - app/views/kern/settings/subscriptions/_plan.html.erb
92
+ - app/views/kern/settings/subscriptions/show.html.erb
81
93
  - app/views/kern/settings/users/show.html.erb
82
94
  - app/views/kern/signups/new.html.erb
83
95
  - app/views/layouts/kern/application.html.erb
84
96
  - app/views/layouts/kern/application.html.erb.tt
85
97
  - app/views/layouts/kern/application/_navigation.html.erb
86
98
  - app/views/layouts/kern/auth.html.erb
99
+ - app/webhooks/stripe/base.rb
100
+ - app/webhooks/stripe/checkout_session_completed.rb
101
+ - app/webhooks/stripe/customer_subscription_deleted.rb
102
+ - app/webhooks/stripe/customer_subscription_updated.rb
87
103
  - bin/dev
88
104
  - bin/rails
89
105
  - bin/release
106
+ - config/initializers/stripe.rb
107
+ - config/initializers/stripe.rb.tt
90
108
  - config/routes.rb
91
109
  - db/migrate/20250101000001_create_users.rb
92
110
  - db/migrate/20250101000002_create_sessions.rb
@@ -94,16 +112,20 @@ files:
94
112
  - db/migrate/20250101000004_create_members.rb
95
113
  - db/migrate/20250101000005_create_roles.rb
96
114
  - db/migrate/20250101000006_create_actors.rb
115
+ - db/migrate/20250101000007_create_billing_profiles.rb
116
+ - db/migrate/20250101000008_create_billing_profile_subscriptions.rb
97
117
  - kern.gemspec
98
118
  - lib/generators/kern/feature/USAGE
99
119
  - lib/generators/kern/feature/feature_generator.rb
100
120
  - lib/generators/kern/helpers.rb
101
121
  - lib/generators/kern/install/USAGE
102
122
  - lib/generators/kern/install/install_generator.rb
123
+ - lib/generators/kern/install/templates/POST_INSTALL
103
124
  - lib/generators/kern/install/templates/configurations/README.md
125
+ - lib/generators/kern/install/templates/configurations/plans.yml
126
+ - lib/generators/kern/install/templates/configurations/stripe.yml
104
127
  - lib/generators/kern/install/templates/configurations/urls.yml
105
- - lib/generators/kern/views/USAGE
106
- - lib/generators/kern/views/views_generator.rb
128
+ - lib/generators/kern/layouts/layouts_generator.rb
107
129
  - lib/kern.rb
108
130
  - lib/kern/config.rb
109
131
  - lib/kern/engine.rb
@@ -133,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
155
  - !ruby/object:Gem::Version
134
156
  version: '0'
135
157
  requirements: []
136
- rubygems_version: 4.0.1
158
+ rubygems_version: 3.6.9
137
159
  specification_version: 4
138
160
  summary: Rails engine with auth, billing, and common components for SaaS apps
139
161
  test_files: []
@@ -1,2 +0,0 @@
1
- /*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-400:oklch(70.4% .191 22.216);--color-red-600:oklch(57.7% .245 27.325);--color-emerald-300:oklch(84.5% .143 164.978);--color-cyan-400:oklch(78.9% .154 211.53);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-blue-950:oklch(28.2% .091 267.935);--color-pink-200:oklch(89.9% .061 343.231);--color-pink-800:oklch(45.9% .187 3.815);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-gray-50:var(--color-slate-50);--color-gray-100:var(--color-slate-100);--color-gray-200:var(--color-slate-200);--color-gray-300:var(--color-slate-300);--color-gray-400:var(--color-slate-400);--color-gray-500:var(--color-slate-500);--color-gray-600:var(--color-slate-600);--color-gray-700:var(--color-slate-700);--color-gray-800:var(--color-slate-800);--color-gray-900:var(--color-slate-900);--color-gray-950:var(--color-slate-950);--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-tight:-.025em;--radius-sm:.25rem;--radius-md:.375rem;--radius-xl:.75rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--blur-md:12px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-brand-50:var(--color-blue-50);--color-brand-300:var(--color-blue-300);--color-brand-400:var(--color-blue-400);--color-brand-500:var(--color-blue-500);--color-brand-600:var(--color-blue-600);--color-brand-700:var(--color-blue-700);--color-brand-800:var(--color-blue-800);--color-brand-950:var(--color-blue-950)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}a:not([class]){text-decoration-line:underline}a:not([class]):hover{text-decoration-line:none}}@layer components{[class^=btn-]{align-items:center;column-gap:calc(var(--spacing)*1);width:100%;padding-inline:calc(var(--spacing)*3.5);padding-block:calc(var(--spacing)*1.25);display:flex}@media (min-width:48rem){[class^=btn-]{width:auto}}[class^=btn-]{text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-tracking:.0125em;letter-spacing:.0125em;border-style:var(--tw-border-style);border-radius:var(--radius-sm);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:transparent;cursor:pointer;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-width:1px;border-color:#0000}[class^=btn-] [data-slot=icon],[class^=btn-] svg{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.btn-secondary{color:var(--color-gray-800);border-color:var(--color-white);--tw-ring-color:var(--color-gray-300)}.btn-secondary [data-slot=icon],.btn-secondary svg{color:var(--color-gray-600)}.btn-secondary:hover{--tw-ring-color:var(--color-gray-300)}.btn-primary{background-color:var(--color-brand-600);color:var(--color-white)}.btn-primary [data-slot=icon],.btn-primary svg{color:var(--color-white)}.btn-primary:hover{background-color:var(--color-brand-700)}.btn-block{width:100%;display:block}.buttons{margin-top:calc(var(--spacing)*4);justify-content:flex-end;display:flex}}@layer utilities{.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.-top-\[400px\]{top:-400px}.top-0{top:calc(var(--spacing)*0)}.-right-\[300px\]{right:-300px}.right-4{right:calc(var(--spacing)*4)}.-bottom-\[450px\]{bottom:-450px}.bottom-4{bottom:calc(var(--spacing)*4)}.-left-\[350px\]{left:-350px}.col-span-12{grid-column:span 12/span 12}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-7{margin-top:calc(var(--spacing)*7)}.mt-8{margin-top:calc(var(--spacing)*8)}.ml-0{margin-left:calc(var(--spacing)*0)}.ml-1{margin-left:calc(var(--spacing)*1)}.block{display:block}.flex{display:flex}.grid{display:grid}.inline{display:inline}.size-3\.5{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.size-4\.5{width:calc(var(--spacing)*4.5);height:calc(var(--spacing)*4.5)}.size-5{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.size-\[800px\]{width:800px;height:800px}.h-8{height:calc(var(--spacing)*8)}.h-dvh{height:100dvh}.h-screen{height:100vh}.w-16{width:calc(var(--spacing)*16)}.w-full{width:100%}.max-w-5xl{max-width:var(--container-5xl)}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-xl{max-width:var(--container-xl)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-32{rotate:32deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.place-items-center{place-items:center}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-x-1{column-gap:calc(var(--spacing)*1)}.gap-x-1\.5{column-gap:calc(var(--spacing)*1.5)}.gap-x-2{column-gap:calc(var(--spacing)*2)}.gap-y-0\.5{row-gap:calc(var(--spacing)*.5)}.gap-y-5{row-gap:calc(var(--spacing)*5)}.overflow-clip{overflow:clip}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200\/50{border-color:#e2e8f080}@supports (color:color-mix(in lab, red, red)){.border-gray-200\/50{border-color:color-mix(in oklab,var(--color-gray-200)50%,transparent)}}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-200\/40{background-color:#e2e8f066}@supports (color:color-mix(in lab, red, red)){.bg-gray-200\/40{background-color:color-mix(in oklab,var(--color-gray-200)40%,transparent)}}.bg-white{background-color:var(--color-white)}.bg-white\/50{background-color:#ffffff80}@supports (color:color-mix(in lab, red, red)){.bg-white\/50{background-color:color-mix(in oklab,var(--color-white)50%,transparent)}}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-gray-900\/80{--tw-gradient-from:#0f172bcc}@supports (color:color-mix(in lab, red, red)){.from-gray-900\/80{--tw-gradient-from:color-mix(in oklab,var(--color-gray-900)80%,transparent)}}.from-gray-900\/80{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-gray-900{--tw-gradient-to:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.fill-brand-400{fill:var(--color-brand-400)}.fill-brand-600\/60{fill:#155dfc99}@supports (color:color-mix(in lab, red, red)){.fill-brand-600\/60{fill:color-mix(in oklab,var(--color-brand-600)60%,transparent)}}.p-2{padding:calc(var(--spacing)*2)}.px-0\.5{padding-inline:calc(var(--spacing)*.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-5{padding-block:calc(var(--spacing)*5)}.pt-4{padding-top:calc(var(--spacing)*4)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.text-center{text-align:center}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.text-brand-500{color:var(--color-brand-500)}.text-brand-600{color:var(--color-brand-600)}.text-current{color:currentColor}.text-cyan-400{color:var(--color-cyan-400)}.text-emerald-300{color:var(--color-emerald-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-gray-950{color:var(--color-gray-950)}.text-red-400{color:var(--color-red-400)}.text-red-600{color:var(--color-red-600)}.text-slate-800{color:var(--color-slate-800)}.text-white{color:var(--color-white)}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.accent-brand-500{accent-color:var(--color-brand-500)}.opacity-100{opacity:1}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-gray-200\/50{--tw-ring-color:#e2e8f080}@supports (color:color-mix(in lab, red, red)){.ring-gray-200\/50{--tw-ring-color:color-mix(in oklab,var(--color-gray-200)50%,transparent)}}.ring-gray-200\/60{--tw-ring-color:#e2e8f099}@supports (color:color-mix(in lab, red, red)){.ring-gray-200\/60{--tw-ring-color:color-mix(in oklab,var(--color-gray-200)60%,transparent)}}.ring-gray-200\/80{--tw-ring-color:#e2e8f0cc}@supports (color:color-mix(in lab, red, red)){.ring-gray-200\/80{--tw-ring-color:color-mix(in oklab,var(--color-gray-200)80%,transparent)}}.ring-gray-700\/90{--tw-ring-color:#314158e6}@supports (color:color-mix(in lab, red, red)){.ring-gray-700\/90{--tw-ring-color:color-mix(in oklab,var(--color-gray-700)90%,transparent)}}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.delay-75{transition-delay:75ms}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.outline-none{--tw-outline-style:none;outline-style:none}:is(.\*\:inline-block>*){display:inline-block}@media (hover:hover){.group-hover\/link\:fill-brand-700:is(:where(.group\/link):hover *){fill:var(--color-brand-700)}.group-hover\/link\:text-brand-700:is(:where(.group\/link):hover *){color:var(--color-brand-700)}.group-hover\/link\:text-brand-950:is(:where(.group\/link):hover *){color:var(--color-brand-950)}.group-hover\/link\:text-gray-950:is(:where(.group\/link):hover *){color:var(--color-gray-950)}}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-50:is(:where(.peer):disabled~*){opacity:.5}.marker\:text-blue-500 ::marker{color:var(--color-blue-500)}.marker\:text-blue-500::marker{color:var(--color-blue-500)}.marker\:text-blue-500 ::-webkit-details-marker{color:var(--color-blue-500)}.marker\:text-blue-500::-webkit-details-marker{color:var(--color-blue-500)}.selection\:bg-pink-200\/40 ::selection{background-color:#fccee866}@supports (color:color-mix(in lab, red, red)){.selection\:bg-pink-200\/40 ::selection{background-color:color-mix(in oklab,var(--color-pink-200)40%,transparent)}}.selection\:bg-pink-200\/40::selection{background-color:#fccee866}@supports (color:color-mix(in lab, red, red)){.selection\:bg-pink-200\/40::selection{background-color:color-mix(in oklab,var(--color-pink-200)40%,transparent)}}.selection\:text-pink-800\/70 ::selection{color:#a2004cb3}@supports (color:color-mix(in lab, red, red)){.selection\:text-pink-800\/70 ::selection{color:color-mix(in oklab,var(--color-pink-800)70%,transparent)}}.selection\:text-pink-800\/70::selection{color:#a2004cb3}@supports (color:color-mix(in lab, red, red)){.selection\:text-pink-800\/70::selection{color:color-mix(in oklab,var(--color-pink-800)70%,transparent)}}.file\:mr-2::file-selector-button{margin-right:calc(var(--spacing)*2)}.file\:rounded-full::file-selector-button{border-radius:3.40282e38px}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-gray-100::file-selector-button{background-color:var(--color-gray-100)}.file\:px-4::file-selector-button{padding-inline:calc(var(--spacing)*4)}.file\:py-1::file-selector-button{padding-block:calc(var(--spacing)*1)}.file\:text-xs::file-selector-button{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.file\:font-medium::file-selector-button{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.file\:text-gray-700::file-selector-button{color:var(--color-gray-700)}@media (hover:hover){.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:border-gray-200:hover{border-color:var(--color-gray-200)}.hover\:bg-brand-50:hover{background-color:var(--color-brand-50)}.hover\:bg-gray-200\/40:hover{background-color:#e2e8f066}@supports (color:color-mix(in lab, red, red)){.hover\:bg-gray-200\/40:hover{background-color:color-mix(in oklab,var(--color-gray-200)40%,transparent)}}.hover\:text-brand-800:hover{color:var(--color-brand-800)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-lg\/5:hover{--tw-shadow-alpha:5%;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,oklab(0% 0 0/.05)),0 4px 6px -4px var(--tw-shadow-color,oklab(0% 0 0/.05));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-gray-300:hover{--tw-ring-color:var(--color-gray-300)}.hover\:file\:bg-gray-200:hover::file-selector-button{background-color:var(--color-gray-200)}}.focus\:bg-white:focus{background-color:var(--color-white)}.focus\:ring:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-0:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-brand-300:focus{--tw-ring-color:var(--color-brand-300)}.focus\:ring-gray-300:focus{--tw-ring-color:var(--color-gray-300)}.focus\:ring-offset-1:focus{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-25:disabled{opacity:.25}.disabled\:opacity-50:disabled{opacity:.5}@media (hover:hover){.disabled\:hover\:ring-gray-200\/80:disabled:hover{--tw-ring-color:#e2e8f0cc}@supports (color:color-mix(in lab, red, red)){.disabled\:hover\:ring-gray-200\/80:disabled:hover{--tw-ring-color:color-mix(in oklab,var(--color-gray-200)80%,transparent)}}}@media (min-width:48rem){.md\:col-span-4{grid-column:span 4/span 4}.md\:mx-4{margin-inline:calc(var(--spacing)*4)}.md\:rounded-md{border-radius:var(--radius-md)}.md\:rounded-xl{border-radius:var(--radius-xl)}.md\:px-4{padding-inline:calc(var(--spacing)*4)}.md\:pb-4{padding-bottom:calc(var(--spacing)*4)}.md\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.md\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.md\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.md\:ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media (min-width:64rem){.lg\:not-sr-only{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:w-60{width:calc(var(--spacing)*60)}.lg\:px-6{padding-inline:calc(var(--spacing)*6)}}@starting-style{.starting\:ml-3\.5{margin-left:calc(var(--spacing)*3.5)}}@starting-style{.starting\:translate-x-4{--tw-translate-x:calc(var(--spacing)*4);translate:var(--tw-translate-x)var(--tw-translate-y)}}@starting-style{.starting\:scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x)var(--tw-scale-y)}}@starting-style{.starting\:opacity-0{opacity:0}}.\[\&\>input\]\:mt-\[0\.15em\]>input{margin-top:.15em}.\[\&\[data-slot\=\'field\'\]\+\[data-slot\=\'field\'\]\]\:mt-4[data-slot=field]+[data-slot=field]{margin-top:calc(var(--spacing)*4)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
@@ -1,22 +0,0 @@
1
- Description:
2
- Generates Kern view templates in your application.
3
-
4
- Example:
5
- rails generate kern:views
6
-
7
- This will generate all available Kern views.
8
-
9
- rails generate kern:views sessions
10
-
11
- This will generate only the sessions views.
12
-
13
- rails generate kern:views sessions passwords
14
-
15
- This will generate sessions and passwords views.
16
-
17
- Available views:
18
- passwords Password reset views
19
- sessions Sign in views
20
- settings User settings views
21
- signups Sign up views
22
- layouts Kern layout files
@@ -1,42 +0,0 @@
1
- module Kern
2
- class ViewsGenerator < Rails::Generators::Base
3
- source_root File.expand_path("../../../../../app/views", __FILE__)
4
-
5
- AVAILABLE_VIEWS = %w[passwords sessions settings signups layouts]
6
-
7
- argument :views, type: :array, default: [], banner: "view view", desc: "Select specific view directories to generate (#{AVAILABLE_VIEWS.join(", ")}). Leave empty for all."
8
-
9
- def copy_views
10
- views_to_generate.map(&:inquiry).each do |view|
11
- verify_view_exists!(view)
12
-
13
- if view.layouts?
14
- generate_layouts
15
- else
16
- directory "kern/#{view}", "app/views/#{view}", recursive: true
17
- end
18
- end
19
- end
20
-
21
- private
22
-
23
- def views_to_generate
24
- views.any? ? views : AVAILABLE_VIEWS
25
- end
26
-
27
- def verify_view_exists!(view)
28
- return if AVAILABLE_VIEWS.include?(view)
29
-
30
- say "View `#{view}` not found. Available views: #{AVAILABLE_VIEWS.join(", ")}", :red
31
-
32
- exit(1)
33
- end
34
-
35
- def generate_layouts
36
- template "layouts/kern/application.html.erb.tt", "app/views/layouts/application.html.erb"
37
- copy_file "layouts/kern/application/_navigation.html.erb", "app/views/layouts/application/_navigation.html.erb"
38
-
39
- copy_file "layouts/kern/auth.html.erb", "app/views/layouts/auth.html.erb"
40
- end
41
- end
42
- end