panda-core 0.10.7 → 0.12.2

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -0
  3. data/app/assets/tailwind/application.css +5 -0
  4. data/app/models/concerns/panda/core/has_uuid.rb +24 -0
  5. data/app/models/panda/core/user.rb +22 -19
  6. data/config/brakeman.ignore +51 -0
  7. data/db/migrate/20250809000001_create_panda_core_users.rb +14 -9
  8. data/db/migrate/20251203100000_rename_is_admin_to_admin_in_panda_core_users.rb +18 -0
  9. data/lib/panda/core/configuration.rb +4 -0
  10. data/lib/panda/core/engine/autoload_config.rb +9 -10
  11. data/lib/panda/core/engine/omniauth_config.rb +82 -36
  12. data/lib/panda/core/engine/route_config.rb +37 -0
  13. data/lib/panda/core/engine.rb +46 -41
  14. data/lib/panda/core/middleware.rb +146 -0
  15. data/lib/panda/core/shared/inflections_config.rb +1 -0
  16. data/lib/panda/core/testing/rails_helper.rb +17 -8
  17. data/lib/panda/core/testing/support/authentication_helpers.rb +6 -6
  18. data/lib/panda/core/testing/support/authentication_test_helpers.rb +2 -12
  19. data/lib/panda/core/testing/support/system/browser_console_logger.rb +1 -2
  20. data/lib/panda/core/testing/support/system/chrome_path.rb +38 -0
  21. data/lib/panda/core/testing/support/system/cuprite_helpers.rb +79 -0
  22. data/lib/panda/core/testing/support/system/cuprite_setup.rb +61 -66
  23. data/lib/panda/core/testing/support/system/system_test_helpers.rb +11 -11
  24. data/lib/panda/core/version.rb +1 -1
  25. data/lib/tasks/panda/core/users.rake +3 -3
  26. data/lib/tasks/panda/shared.rake +31 -5
  27. data/public/panda-core-assets/favicons/browserconfig.xml +1 -1
  28. data/public/panda-core-assets/favicons/site.webmanifest +1 -1
  29. data/public/panda-core-assets/panda-core-0.11.0.css +2 -0
  30. data/public/panda-core-assets/panda-core.css +1 -1
  31. metadata +9 -9
  32. data/db/migrate/20250810120000_add_current_theme_to_panda_core_users.rb +0 -7
  33. data/db/migrate/20250811120000_add_oauth_avatar_url_to_panda_core_users.rb +0 -7
  34. data/lib/panda/core/engine/middleware_config.rb +0 -17
  35. data/lib/panda/core/testing/support/system/better_system_tests.rb +0 -180
  36. data/lib/panda/core/testing/support/system/capybara_config.rb +0 -64
  37. data/lib/panda/core/testing/support/system/ci_capybara_config.rb +0 -77
  38. data/public/panda-core-assets/panda-core-0.10.6.css +0 -2
@@ -1,180 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Extends Rails system tests with improved screenshot and driver handling
4
- #
5
- # This module provides enhancements to Rails system tests:
6
- # - Multi-session screenshot support
7
- # - CI-specific error handling and logging
8
- # - Enhanced failure screenshots with HTML debugging
9
- # - Network idle waiting before screenshots
10
- #
11
- # @example Using in RSpec
12
- # RSpec.configure do |config|
13
- # config.include Panda::Core::Testing::BetterSystemTests, type: :system
14
- # end
15
- module Panda
16
- module Core
17
- module Testing
18
- module BetterSystemTests
19
- # Make failure screenshots compatible with multi-session setup.
20
- # That's where we use Capybara.last_used_session introduced before.
21
- def take_screenshot
22
- return super unless Capybara.last_used_session
23
-
24
- Capybara.using_session(Capybara.last_used_session) { super }
25
- end
26
-
27
- module ClassMethods
28
- # Configure better system tests for RSpec
29
- # This sets up the necessary hooks and configuration
30
- def configure_better_system_tests!
31
- # Make urls in mailers contain the correct server host.
32
- # This is required for testing links in emails (e.g., via capybara-email).
33
- around(:each, type: :system) do |ex|
34
- was_host = Rails.application.default_url_options[:host]
35
- Rails.application.default_url_options[:host] = Capybara.server_host
36
- ex.run
37
- Rails.application.default_url_options[:host] = was_host
38
- end
39
-
40
- # Make sure this hook runs before others
41
- # Means you don't have to set js: true in every system spec
42
- prepend_before(:each, type: :system) do
43
- driven_by :cuprite
44
- end
45
-
46
- # Enable automatic screenshots on failure
47
- # Add CI-specific timeout and retry logic for form interactions
48
- # Includes MultipleExceptionError detection
49
- around(:each, type: :system) do |example|
50
- exception = nil
51
-
52
- begin
53
- example.run
54
- rescue => e
55
- exception = e
56
- end
57
-
58
- # Also check example.exception in case RSpec aggregated exceptions there
59
- exception ||= example.exception
60
-
61
- # Handle MultipleExceptionError specially - don't retry, just report and skip
62
- if exception.is_a?(RSpec::Core::MultipleExceptionError)
63
- puts "\n" + ("=" * 80)
64
- puts "⚠️ MULTIPLE EXCEPTIONS - SKIPPING TEST (NO RETRY)"
65
- puts "=" * 80
66
- puts "Test: #{example.full_description}"
67
- puts "File: #{example.metadata[:file_path]}:#{example.metadata[:line_number]}"
68
- puts "Total exceptions: #{exception.all_exceptions.count}"
69
- puts "=" * 80
70
-
71
- # Group exceptions by class for cleaner output
72
- exceptions_by_class = exception.all_exceptions.group_by(&:class)
73
- exceptions_by_class.each do |klass, exs|
74
- puts "\n#{klass.name} (#{exs.count} occurrence#{"s" if exs.count > 1}):"
75
- puts " #{exs.first.message.split("\n").first}"
76
- end
77
-
78
- puts "\n" + ("=" * 80)
79
- puts "⚠️ Skipping retry - moving to next test"
80
- puts "=" * 80 + "\n"
81
-
82
- # Mark this so after hooks can skip verbose output
83
- example.metadata[:multiple_exception_detected] = true
84
-
85
- # Re-raise to mark test as failed, but don't retry
86
- raise exception
87
- end
88
-
89
- # For other exceptions in CI, log and re-raise
90
- if exception && ENV["GITHUB_ACTIONS"] == "true"
91
- puts "[CI] Test error detected: #{exception.class} - #{exception.message}"
92
- puts "[CI] Current URL: #{begin
93
- page.current_url
94
- rescue
95
- ""
96
- end}"
97
- raise exception
98
- elsif exception
99
- # Not in CI, just re-raise
100
- raise exception
101
- end
102
- end
103
-
104
- after(:each, type: :system) do |example|
105
- next unless example.exception
106
-
107
- begin
108
- # Wait for any pending JavaScript to complete
109
- # Cuprite has direct network idle support
110
- begin
111
- page.driver.wait_for_network_idle
112
- rescue
113
- nil
114
- end
115
-
116
- # Wait for DOM to be ready
117
- # sleep 0.5
118
-
119
- # Get comprehensive page info
120
- page_html = begin
121
- page.html
122
- rescue
123
- ""
124
- end
125
- current_url = begin
126
- page.current_url
127
- rescue
128
- ""
129
- end
130
- current_path = begin
131
- page.current_path
132
- rescue
133
- ""
134
- end
135
- page_title = begin
136
- page.title
137
- rescue
138
- ""
139
- end
140
-
141
- # Check for redirect or blank page indicators
142
- if page_html.length < 100
143
- puts "Warning: Page content appears minimal (#{page_html.length} chars) when taking screenshot"
144
- end
145
-
146
- # Use Capybara's save_screenshot method
147
- screenshot_path = Capybara.save_screenshot
148
- if screenshot_path
149
- puts "Screenshot saved to: #{screenshot_path}"
150
- puts "Page title: #{page_title}" if page_title.present?
151
- puts "Current URL: #{current_url}" if current_url.present?
152
- puts "Current path: #{current_path}" if current_path.present?
153
- puts "Page content length: #{page_html.length} characters"
154
-
155
- # Save page HTML for debugging in CI
156
- if ENV["GITHUB_ACTIONS"] && page_html.present?
157
- html_debug_path = screenshot_path.gsub(".png", ".html")
158
- File.write(html_debug_path, page_html)
159
- puts "Page HTML saved to: #{html_debug_path}"
160
- end
161
- end
162
- rescue => e
163
- puts "Failed to capture screenshot: #{e.message}"
164
- # Skip verbose output if already handled by MultipleExceptionError handler
165
- unless example.metadata[:multiple_exception_detected]
166
- puts "Exception class: #{example.exception.class}"
167
- puts "Exception message: #{example.exception.message}"
168
- end
169
- end
170
- end
171
- end
172
- end
173
-
174
- def self.included(base)
175
- base.extend(ClassMethods)
176
- end
177
- end
178
- end
179
- end
180
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Shared Capybara configuration for all Panda gems
4
- # This provides standard Capybara setup with sensible defaults
5
-
6
- # We don't want to hang around for too long for processes at the moment
7
- Capybara.default_max_wait_time = ENV["CAPYBARA_MAX_WAIT_TIME"].present? ? ENV["CAPYBARA_MAX_WAIT_TIME"].to_i : 2
8
-
9
- # Normalize whitespaces when using `has_text?` and similar matchers,
10
- # i.e., ignore newlines, trailing spaces, etc.
11
- # That makes tests less dependent on slight UI changes.
12
- Capybara.default_normalize_ws = true
13
-
14
- # Where to store system tests artifacts (e.g. screenshots, downloaded files, etc.).
15
- # It could be useful to be able to configure this path from the outside (e.g., on CI).
16
- Capybara.save_path = ENV.fetch("CAPYBARA_ARTIFACTS", "./tmp/capybara")
17
-
18
- # Disable animation so we're not waiting for it
19
- Capybara.disable_animation = true
20
-
21
- # See SystemTestHelpers#take_screenshot
22
- # This allows us to track which session was last used for proper screenshot naming
23
- Capybara.singleton_class.prepend(Module.new do
24
- attr_accessor :last_used_session
25
-
26
- def using_session(name, &block)
27
- self.last_used_session = name
28
- super
29
- ensure
30
- self.last_used_session = nil
31
- end
32
- end)
33
-
34
- # Configure server host and port
35
- # This is loaded for all environments
36
- # → you do not want fixed ports there
37
- Capybara.server_host = "127.0.0.1"
38
- Capybara.server_port = ENV["CAPYBARA_PORT"]&.to_i # Let Capybara choose if not specified
39
-
40
- # Configure Puma server with explicit options
41
- # Use single-threaded mode to share database connection with tests
42
- Capybara.register_server :puma do |app, port, host|
43
- require "rack/handler/puma"
44
- Rack::Handler::Puma.run(app, Port: port, Host: host, Silent: true, Threads: "1:1")
45
- end
46
- Capybara.server = :puma
47
-
48
- # Do not set app_host here - let Capybara determine it from the server
49
- # This avoids conflicts between what's configured and what's actually running
50
-
51
- # RSpec configuration for Capybara
52
- RSpec.configure do |config|
53
- # Save screenshots on system test failures
54
- config.after(:each, type: :system) do |example|
55
- if example.exception
56
- timestamp = Time.now.strftime("%Y-%m-%d-%H%M%S")
57
- "tmp/capybara/failures/#{example.full_description.parameterize}_#{timestamp}"
58
-
59
- # Screenshots are saved automatically by Capybara, but we could save additional artifacts here
60
- # save_page("#{filename_base}.html")
61
- # save_screenshot("#{filename_base}.png", full: true)
62
- end
63
- end
64
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # CI-specific Capybara configuration
4
- # This file configures Capybara for GitHub Actions and other CI environments
5
- # It uses a more robust Puma setup with compatibility for Puma 6 and 7+
6
-
7
- return unless defined?(Capybara)
8
-
9
- ci_mode = ENV["GITHUB_ACTIONS"] == "true" || ENV["CI_SYSTEM_SPECS"] == "true"
10
- return unless ci_mode
11
-
12
- require "rack/handler/puma"
13
-
14
- RSpec.configure do |config|
15
- config.before(:suite) do
16
- Capybara.server = :puma_ci
17
- Capybara.default_max_wait_time = Integer(ENV.fetch("CAPYBARA_MAX_WAIT_TIME", 5))
18
-
19
- port = Integer(ENV.fetch("CAPYBARA_PORT", 3001))
20
- Capybara.server_host = "127.0.0.1"
21
- Capybara.server_port = port
22
- Capybara.app_host = "http://127.0.0.1:#{port}"
23
- Capybara.always_include_port = true
24
-
25
- puts "[CI Config] Capybara.server = #{Capybara.server.inspect}"
26
- puts "[CI Config] Capybara.app_host = #{Capybara.app_host.inspect}"
27
- puts "[CI Config] Capybara.server_host = #{Capybara.server_host.inspect}"
28
- puts "[CI Config] Capybara.server_port = #{Capybara.server_port.inspect}"
29
- puts "[CI Config] Capybara.max_wait = #{Capybara.default_max_wait_time}s"
30
- end
31
- end
32
-
33
- Capybara.register_server :puma_ci do |app, port, host|
34
- puts "[CI Config] Starting Puma (single mode) on #{host}:#{port}"
35
-
36
- min_threads = Integer(ENV.fetch("PUMA_MIN_THREADS", "2"))
37
- max_threads = Integer(ENV.fetch("PUMA_MAX_THREADS", "2"))
38
-
39
- # Wrap the app to catch startup errors
40
- wrapped_app = proc do |env|
41
- app.call(env)
42
- rescue => e
43
- puts "[CI ERROR] Rails middleware error during request:"
44
- puts "[CI ERROR] #{e.class}: #{e.message}"
45
- puts "[CI ERROR] Backtrace:"
46
- puts e.backtrace.first(10).join("\n")
47
- raise e
48
- end
49
-
50
- # Allow conditional silencing of server logs via CAPYBARA_SERVER_VERBOSE
51
- verbose = ENV.fetch("CAPYBARA_SERVER_VERBOSE", "false") == "true"
52
-
53
- options = {
54
- Host: host,
55
- Port: port,
56
- Threads: "#{min_threads}:#{max_threads}",
57
- Workers: 0,
58
- Silent: !verbose,
59
- Verbose: verbose,
60
- PreloadApp: false
61
- }
62
-
63
- # --- Puma Compatibility Layer (supports Puma 6 AND Puma 7+) ---
64
- puma_run = Rack::Handler::Puma.method(:run)
65
-
66
- if puma_run.arity == 2
67
- # Puma <= 6.x signature:
68
- # run(app, options_hash)
69
- puts "[CI Config] Using Puma <= 6 API (arity 2)"
70
- Rack::Handler::Puma.run(wrapped_app, options)
71
- else
72
- # Puma >= 7.x signature:
73
- # run(app, **options)
74
- puts "[CI Config] Using Puma >= 7 API (keyword args)"
75
- Rack::Handler::Puma.run(wrapped_app, **options)
76
- end
77
- end
@@ -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-border-style:solid;--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-leading:initial;--tw-duration:initial;--tw-font-weight:initial;--tw-space-y-reverse:0;--tw-tracking:initial;--tw-outline-style:solid}}}@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-serif:ui-serif,Georgia,Cambria,"Times New Roman",Times,serif;--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-indigo-500:var(--color-primary-500);--color-indigo-600:var(--color-primary-600);--color-gray-50:var(--color-gray-50);--color-gray-100:var(--color-gray-100);--color-gray-200:var(--color-gray-200);--color-gray-300:var(--color-gray-300);--color-gray-400:var(--color-gray-400);--color-gray-500:var(--color-gray-500);--color-gray-600:var(--color-gray-600);--color-gray-700:var(--color-gray-700);--color-gray-800:var(--color-gray-800);--color-gray-900:var(--color-gray-900);--color-gray-950:var(--color-gray-950);--color-black:var(--color-black);--color-white:var(--color-white);--spacing:.25rem;--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);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-wide:.025em;--radius-md:.375rem;--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-light:var(--color-light);--color-mid:var(--color-mid);--color-dark:var(--color-dark);--color-highlight:var(--color-highlight);--color-active:var(--color-active);--color-inactive:var(--color-inactive);--color-warning:var(--color-warning);--color-error:var(--color-error);--color-primary-50:var(--color-primary-50);--color-primary-100:var(--color-primary-100);--color-primary-200:var(--color-primary-200);--color-primary-300:var(--color-primary-300);--color-primary-400:var(--color-primary-400);--color-primary-500:var(--color-primary-500);--color-primary-600:var(--color-primary-600);--color-primary-700:var(--color-primary-700);--color-primary-800:var(--color-primary-800);--color-primary-900:var(--color-primary-900);--color-primary-950:var(--color-primary-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}html[data-theme=default]{--color-white:#f9f9f9;--color-black:#1a161d;--color-light:#eecee6;--color-mid:#8d5eb7;--color-dark:#211d49;--color-highlight:#d04014;--color-active:#008755;--color-warning:#facf8e;--color-inactive:#d8f7f5;--color-error:#f58181;--color-primary-50:#faf5ff;--color-primary-100:#f3e8ff;--color-primary-200:#e9d5ff;--color-primary-300:#d8b4fe;--color-primary-400:#c084fc;--color-primary-500:#8d5eb7;--color-primary-600:#7850a0;--color-primary-700:#64418c;--color-primary-800:#3c2d64;--color-primary-900:#211d49;--color-primary-950:#181432;--color-gray-50:#f9fafb;--color-gray-100:#f3f4f6;--color-gray-200:#e5e7eb;--color-gray-300:#d1d5db;--color-gray-400:#9ca3af;--color-gray-500:#6b7280;--color-gray-600:#4b5563;--color-gray-700:#374151;--color-gray-800:#1f2937;--color-gray-900:#111827;--color-gray-950:#030712}html[data-theme=sky]{--color-white:#f9f9f9;--color-black:#1a161d;--color-light:#cceef2;--color-mid:#2a669f;--color-dark:#14204a;--color-highlight:#d04014;--color-active:#459a59;--color-warning:#f4be66;--color-inactive:#d8f7f5;--color-error:#d04014;--color-primary-50:#f0f9ff;--color-primary-100:#e0f2fe;--color-primary-200:#bae6fd;--color-primary-300:#7dd3fc;--color-primary-400:#38bdf8;--color-primary-500:#2a669f;--color-primary-600:#235584;--color-primary-700:#1c446a;--color-primary-800:#163452;--color-primary-900:#14204a;--color-primary-950:#0f1837;--color-gray-50:#f9fafb;--color-gray-100:#f3f4f6;--color-gray-200:#e5e7eb;--color-gray-300:#d1d5db;--color-gray-400:#9ca3af;--color-gray-500:#6b7280;--color-gray-600:#4b5563;--color-gray-700:#374151;--color-gray-800:#1f2937;--color-gray-900:#111827;--color-gray-950:#030712}a.block-link:after{content:"";position:absolute;inset:0}html[data-theme=default] .bg-gradient-admin{background:linear-gradient(to bottom right,#211d49,#8d5eb7)}html[data-theme=sky] .bg-gradient-admin{background:linear-gradient(to bottom right,#14204a,#2a669f)}}@layer components{input[type=text],input[type=email],input[type=password],input[type=url],input[type=tel],input[type=number],input[type=date],input[type=datetime-local],input[type=month],input[type=week],input[type=time],input[type=search],textarea{border-radius:var(--radius-md);border-style:var(--tw-border-style);background-color:var(--color-white);width:100%;padding:calc(var(--spacing)*2);color:var(--color-gray-900);--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:var(--color-mid);--tw-ring-inset:inset;border-width:0;display:block}:is(input[type=text],input[type=email],input[type=password],input[type=url],input[type=tel],input[type=number],input[type=date],input[type=datetime-local],input[type=month],input[type=week],input[type=time],input[type=search],textarea)::placeholder{color:var(--color-gray-300)}:is(input[type=text],input[type=email],input[type=password],input[type=url],input[type=tel],input[type=number],input[type=date],input[type=datetime-local],input[type=month],input[type=week],input[type=time],input[type=search],textarea):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);--tw-ring-color:var(--color-dark);--tw-ring-inset:inset}@media (hover:hover){:is(input[type=text],input[type=email],input[type=password],input[type=url],input[type=tel],input[type=number],input[type=date],input[type=datetime-local],input[type=month],input[type=week],input[type=time],input[type=search],textarea):hover{cursor:pointer}}@media (min-width:40rem){input[type=text],input[type=email],input[type=password],input[type=url],input[type=tel],input[type=number],input[type=date],input[type=datetime-local],input[type=month],input[type=week],input[type=time],input[type=search],textarea{--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}}:is(input[type=text],input[type=email],input[type=password],input[type=url],input[type=tel],input[type=number],input[type=date],input[type=datetime-local],input[type=month],input[type=week],input[type=time],input[type=search],textarea):disabled{cursor:not-allowed;background-color:var(--color-gray-50);--tw-ring-color:var(--color-gray-300)}:is(input[type=text],input[type=email],input[type=password],input[type=url],input[type=tel],input[type=number],input[type=date],input[type=datetime-local],input[type=month],input[type=week],input[type=time],input[type=search],textarea):disabled:focus{--tw-ring-color:var(--color-gray-300)}select{border-radius:var(--radius-md);border-style:var(--tw-border-style);background-color:var(--color-white);width:100%;padding-block:calc(var(--spacing)*1.5);padding-right:calc(var(--spacing)*10);padding-left:calc(var(--spacing)*3);color:var(--color-gray-900);--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:var(--color-mid);--tw-ring-inset:inset;border-width:0;display:block}select: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);--tw-ring-color:var(--color-dark);--tw-ring-inset:inset}@media (hover:hover){select:hover{cursor:pointer}}@media (min-width:40rem){select{--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}}select:disabled{cursor:not-allowed;background-color:var(--color-gray-50);--tw-ring-color:var(--color-gray-300)}select:disabled:focus{--tw-ring-color:var(--color-gray-300)}select{appearance:none;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em}input[type=checkbox],input[type=radio]{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);border-color:var(--color-gray-300);background-color:var(--color-white);color:var(--color-indigo-600);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;border-radius:.25rem;transition-duration:.2s}:is(input[type=checkbox],input[type=radio]):focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + 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:var(--color-indigo-500);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}:is(input[type=checkbox],input[type=radio]):disabled{cursor:not-allowed;background-color:var(--color-gray-100)}input[type=radio]{border-radius:3.40282e38px}label{margin-bottom:calc(var(--spacing)*1);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);color:var(--color-gray-700);display:block}.field{margin-bottom:calc(var(--spacing)*4)}input.error,textarea.error,select.error,:is(input.error,textarea.error,select.error):focus{--tw-ring-color:var(--color-red-500)}.field-error{margin-top:calc(var(--spacing)*1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-600)}.btn{border-radius:var(--radius-md);padding-inline:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*3);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;justify-content:center;align-items:center;transition-duration:.2s;display:inline-flex}.btn:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + 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-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.btn:disabled{cursor:not-allowed;opacity:.5}.btn-primary{background-color:var(--color-mid);color:var(--color-white)}@media (hover:hover){.btn-primary:hover{opacity:.9}}.btn-primary:focus{--tw-ring-color:var(--color-mid)}.btn-secondary{background-color:var(--color-gray-200);color:var(--color-gray-900)}@media (hover:hover){.btn-secondary:hover{background-color:var(--color-gray-300)}}.btn-secondary:focus{--tw-ring-color:var(--color-gray-500)}.btn-danger{background-color:var(--color-red-600);color:var(--color-white)}@media (hover:hover){.btn-danger:hover{background-color:var(--color-red-700)}}.btn-danger:focus{--tw-ring-color:var(--color-red-500)}a.block-link:after{content:"";position:absolute;inset:0}:where(.codex-editor__redactor .ce-block .ce-block__content>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(1.6rem*var(--tw-space-y-reverse));margin-block-end:calc(1.6rem*calc(1 - var(--tw-space-y-reverse)))}.codex-editor__redactor .ce-block .ce-block__content{font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:1.6;--tw-font-weight:var(--font-weight-normal);line-height:1.6;font-weight:var(--font-weight-normal);color:var(--color-dark)}.codex-editor__redactor .ce-block .ce-block__content h1.ce-header{font-family:var(--font-sans);font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height));--tw-leading:1.2;--tw-font-weight:var(--font-weight-semibold);line-height:1.2;font-weight:var(--font-weight-semibold)}@media (min-width:48rem){.codex-editor__redactor .ce-block .ce-block__content h1.ce-header{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}}.codex-editor__redactor .ce-block .ce-block__content h2.ce-header{margin-top:calc(var(--spacing)*8);margin-bottom:calc(var(--spacing)*4);font-family:var(--font-sans);font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-leading:1.3;--tw-font-weight:var(--font-weight-medium);line-height:1.3;font-weight:var(--font-weight-medium)}.codex-editor__redactor .ce-block .ce-block__content h3.ce-header{margin-top:calc(var(--spacing)*6);margin-bottom:calc(var(--spacing)*4);font-family:var(--font-sans);font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-leading:1.3;--tw-font-weight:var(--font-weight-normal);line-height:1.3;font-weight:var(--font-weight-normal)}.codex-editor__redactor .ce-block .ce-block__content p,.codex-editor__redactor .ce-block .ce-block__content li{--tw-leading:1.6;--tw-tracking:var(--tracking-wide);max-width:85ch;letter-spacing:var(--tracking-wide);line-height:1.6}:is(.codex-editor__redactor .ce-block .ce-block__content p,.codex-editor__redactor .ce-block .ce-block__content li) a{color:#1a9597;text-underline-offset:2px;text-decoration-line:underline}@media (hover:hover){:is(.codex-editor__redactor .ce-block .ce-block__content p,.codex-editor__redactor .ce-block .ce-block__content li) a:hover{color:#158486}}:is(.codex-editor__redactor .ce-block .ce-block__content p,.codex-editor__redactor .ce-block .ce-block__content li) a:focus{outline-style:var(--tw-outline-style);outline-offset:2px;outline-width:2px;outline-color:#1a9597}:is(.codex-editor__redactor .ce-block .ce-block__content p,.codex-editor__redactor .ce-block .ce-block__content li) strong,:is(.codex-editor__redactor .ce-block .ce-block__content p,.codex-editor__redactor .ce-block .ce-block__content li) b{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.codex-editor__redactor .ce-block .ce-block__content p{margin-bottom:calc(var(--spacing)*4)}.codex-editor__redactor .ce-block .ce-block__content .cdx-quote{margin-bottom:calc(var(--spacing)*4);border-left-style:var(--tw-border-style);border-left-width:8px;border-left-color:var(--color-inactive);padding:calc(var(--spacing)*6);background-color:#eef0f3}.codex-editor__redactor .ce-block .ce-block__content .cdx-quote .cdx-quote__caption{margin-top:calc(var(--spacing)*2);margin-left:calc(var(--spacing)*6);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-dark);display:block}.codex-editor__redactor .ce-block .ce-block__content .cdx-quote .cdx-quote__text{quotes:"“" "”" "‘" "’";padding-left:calc(var(--spacing)*6)}.codex-editor__redactor .ce-block .ce-block__content .cdx-quote .cdx-quote__text:before{margin-right:calc(var(--spacing)*2);margin-left:calc(var(--spacing)*-8);vertical-align:text-bottom;font-family:var(--font-serif);font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height));--tw-leading:calc(var(--spacing)*4);line-height:calc(var(--spacing)*4);color:var(--color-dark);content:open-quote}.codex-editor__redactor .ce-block .ce-block__content .cdx-quote .cdx-quote__text p{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));font-style:italic;display:inline}.codex-editor__redactor .ce-block .ce-block__content .cdx-list{margin-bottom:calc(var(--spacing)*4);padding-left:calc(var(--spacing)*6)}--ordered:is(.codex-editor__redactor .ce-block .ce-block__content .cdx-list){list-style-type:decimal}--unordered:is(.codex-editor__redactor .ce-block .ce-block__content .cdx-list){list-style-type:disc}.codex-editor__redactor .ce-block .ce-block__content .cdx-list .cdx-list{margin-top:calc(var(--spacing)*2);margin-bottom:calc(var(--spacing)*0)}.codex-editor__redactor .ce-block .ce-block__content .cdx-list .cdx-list__item{margin-bottom:calc(var(--spacing)*2);padding-left:calc(var(--spacing)*2)}.codex-editor__redactor .ce-block .ce-block__content .cdx-nested-list{margin-bottom:calc(var(--spacing)*4);padding-left:calc(var(--spacing)*6)}--ordered:is(.codex-editor__redactor .ce-block .ce-block__content .cdx-nested-list){list-style-type:decimal}--unordered:is(.codex-editor__redactor .ce-block .ce-block__content .cdx-nested-list){list-style-type:disc}.codex-editor__redactor .ce-block .ce-block__content .cdx-nested-list .cdx-nested-list{margin-top:calc(var(--spacing)*2);margin-bottom:calc(var(--spacing)*0)}.codex-editor__redactor .ce-block .ce-block__content .cdx-nested-list .cdx-nested-list__item{margin-bottom:calc(var(--spacing)*2);padding-left:calc(var(--spacing)*2)}.codex-editor__redactor .ce-block .ce-block__content .cdx-table{margin-block:calc(var(--spacing)*6);border-collapse:collapse;border-style:var(--tw-border-style);border-width:2px;border-color:var(--color-dark);width:100%}__head:is(.codex-editor__redactor .ce-block .ce-block__content .cdx-table){border-right-style:var(--tw-border-style);border-right-width:2px;border-color:var(--color-dark);background-color:var(--color-light);padding:calc(var(--spacing)*3);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}__row:is(.codex-editor__redactor .ce-block .ce-block__content .cdx-table){border-bottom-style:var(--tw-border-style);border-bottom-width:2px;border-color:var(--color-dark)}__cell:is(.codex-editor__redactor .ce-block .ce-block__content .cdx-table){border-right-style:var(--tw-border-style);border-right-width:2px;border-color:var(--color-dark);padding:calc(var(--spacing)*3)}.codex-editor__redactor .ce-block .ce-block__content .cdx-embed iframe{--tw-border-style:none;border-style:none;width:100%}}@layer utilities;@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@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-leading{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}