cyperful 0.1.3 → 0.1.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0cc8a2ac49a7d7223268f31ec69e960e1fa5bf48fa98a92a5f3adc7b14776f7
4
- data.tar.gz: 73be4bde0f5750a4366814527e890a9425764b885c4c72e5bea3717a9dfb6fa6
3
+ metadata.gz: 61dce63e0bff592ded11835cf202b241d717acaaf99331db8f447d3d7dc2aba9
4
+ data.tar.gz: a2ec8532880c523e33ce15d377a1fc28b6832354306c784fa3dea7a38200302b
5
5
  SHA512:
6
- metadata.gz: 3b67bf8da9159ff69167d5397a6e87f04980df7504cd46946710d8f28e3ead1a2ccf43738060c38552542d841ae76c6df70bda92d0e20fb9d5f9fc3395551f08
7
- data.tar.gz: 5c3cdef286ca10f0f609f3cfd550c138747fa71af31ed9a8ed73006f8b422d46d1b75492383d6f1be83bf6c8ab196e4584522bbeacffa5f9a385920ece28ca06
6
+ metadata.gz: 0cd35d0e7f3ee879367682ef2780fd6904d879a2d0a6c95a936191c3054b9cbfe60f682d4b911804565050b4da8a75744f5f8f58ef41d8fe82c02e1de84278de
7
+ data.tar.gz: 5755db30366159e25279fde63c4e2ccc2f94c934dee4538b237b8e3aee05774ff0182ee8e006d21aaf525ed0b8bdb75675a2fdbeb2879d0ab8fe66d92ecbd4ff
@@ -74,6 +74,7 @@ class Cyperful::Driver
74
74
  )
75
75
  end
76
76
 
77
+ # NOTE: there can be multiple steps per line, this takes the last instance
77
78
  @step_per_line = @steps.index_by { |step| step[:line] }
78
79
 
79
80
  @current_step = nil
@@ -110,21 +111,22 @@ class Cyperful::Driver
110
111
  @tracepoint.enable
111
112
  end
112
113
 
113
- # Every time a file changes the `test/` directory,
114
- # reset this test
115
- # TODO: add an option to auto-run
114
+ # Every time a file changes the `test/` directory, reset this test
115
+ # TODO: add an option to auto-run on reload
116
116
  def setup_file_listener
117
- test_dir = @source_filepath.match(%r{^/.+/(test|spec)\b})[0]
117
+ # TODO: we need to somehow reload the source files
118
118
 
119
- @file_listener&.stop
120
- @file_listener =
121
- Listen.to(test_dir) do |_modified, _added, _removed|
122
- puts "Test files changed, resetting test..."
119
+ # test_dir = @source_filepath.match(%r{^/.+/(?:test|spec)\b})[0]
123
120
 
124
- @pause_at_step = true
125
- @step_pausing_queue.enq(:reset)
126
- end
127
- @file_listener.start
121
+ # @file_listener&.stop
122
+ # @file_listener =
123
+ # Listen.to(test_dir) do |_modified, _added, _removed|
124
+ # puts "Test files changed, resetting test..."
125
+
126
+ # @pause_at_step = true
127
+ # @step_pausing_queue.enq(:reset)
128
+ # end
129
+ # @file_listener.start
128
130
  end
129
131
 
130
132
  def print_steps
@@ -149,7 +151,7 @@ class Cyperful::Driver
149
151
  "running"
150
152
  end
151
153
 
152
- def test_duration_ms
154
+ private def test_duration_ms
153
155
  start_at = @steps.first&.[](:start_at)
154
156
  return nil unless start_at
155
157
  last_ended_step_i = @steps.rindex { |step| step[:end_at] }
@@ -234,12 +236,27 @@ class Cyperful::Driver
234
236
  def drive_iframe
235
237
  puts "Driving iframe..."
236
238
 
237
- @session.switch_to_frame(
238
- @session.find(:css, "iframe#scenario-frame"), # waits for the iframe to load
239
- )
239
+ # make sure a `within` block doesn't affect these commands
240
+ without_finder_scopes do
241
+ @session.switch_to_frame(
242
+ # `find` waits for the iframe to load
243
+ @session.find(:css, "iframe#scenario-frame"),
244
+ )
245
+ end
246
+
240
247
  @driving_iframe = true
241
248
  end
242
249
 
250
+ private def without_finder_scopes(&block)
251
+ scopes = @session.send(:scopes)
252
+ before_scopes = scopes.dup
253
+ scopes.reject! { |el| el.is_a?(Capybara::Node::Element) }
254
+ block.call
255
+ ensure
256
+ scopes.clear
257
+ scopes.push(*before_scopes)
258
+ end
259
+
243
260
  # forked from: https://github.com/teamcapybara/capybara/blob/master/lib/capybara/session.rb#L264
244
261
  private def make_absolute_url(visit_uri)
245
262
  visit_uri = ::Addressable::URI.parse(visit_uri.to_s)
@@ -268,8 +285,17 @@ class Cyperful::Driver
268
285
 
269
286
  WATCHER_JS = File.read(File.join(Cyperful::ROOT_DIR, "watcher.js"))
270
287
 
288
+ private def skip_multi_sessions
289
+ unless Capybara.current_session == @session
290
+ warn "Skipped Cyperful setup in non-default session: #{Capybara.session_name}"
291
+ return true
292
+ end
293
+ false
294
+ end
295
+
271
296
  def internal_visit(url)
272
297
  return false unless @driving_iframe
298
+ return false if skip_multi_sessions
273
299
 
274
300
  abs_url, display_url = make_absolute_url(url)
275
301
 
@@ -291,6 +317,7 @@ class Cyperful::Driver
291
317
 
292
318
  def internal_current_url
293
319
  return nil unless @driving_iframe
320
+ return nil if skip_multi_sessions
294
321
 
295
322
  @session.evaluate_script("window.location.href")
296
323
  end
@@ -335,9 +362,6 @@ class Cyperful::Driver
335
362
  @tracepoint&.disable
336
363
  @tracepoint = nil
337
364
 
338
- @file_listener&.stop
339
- @file_listener = nil
340
-
341
365
  if error&.is_a?(Cyperful::ResetCommand)
342
366
  puts "\nPlease ignore the error, we're just resetting the test ;)"
343
367
 
@@ -355,9 +379,10 @@ class Cyperful::Driver
355
379
  backtrace = []
356
380
  error.backtrace.each do |s|
357
381
  i ||= 0 if s.include?(@source_filepath)
358
- i += 1 if i
359
- break if i && i > 4
382
+ next unless i
383
+ i += 1
360
384
  backtrace << s
385
+ break if i >= 6
361
386
  end
362
387
 
363
388
  warn "\n\nTest failed with error:\n#{error.message}\n#{backtrace.join("\n")}"
@@ -372,5 +397,8 @@ class Cyperful::Driver
372
397
  puts "Cyperful teardown complete. Waiting for command..."
373
398
  command = @step_pausing_queue.deq
374
399
  queue_reset if command == :reset
400
+ ensure
401
+ @file_listener&.stop
402
+ @file_listener = nil
375
403
  end
376
404
  end
@@ -17,9 +17,24 @@ module PrependCapybaraSession
17
17
  return if Cyperful.current&.internal_visit(current_url)
18
18
  super
19
19
  end
20
+
21
+ def go_back
22
+ super
23
+ Cyperful.current&.drive_iframe
24
+ end
20
25
  end
21
26
  Capybara::Session.prepend(PrependCapybaraSession)
22
27
 
28
+ module PrependCapybaraWindow
29
+ # this solves a bug in Capybara where it doesn't
30
+ # return to driving the iframe after a call to `Window#close`
31
+ def close
32
+ super
33
+ Cyperful.current&.drive_iframe
34
+ end
35
+ end
36
+ Capybara::Window.prepend(PrependCapybaraWindow)
37
+
23
38
  # The Minitest test helper.
24
39
  # TODO: support other test frameworks like RSpec
25
40
  module Cyperful::SystemTestHelper
@@ -1,6 +1,41 @@
1
1
  require "parser/current"
2
+ require "capybara/minitest"
2
3
 
3
4
  class Cyperful::TestParser
5
+ # see docs for methods: https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Session
6
+ @step_at_methods =
7
+ Capybara::Session::DSL_METHODS.to_set +
8
+ Capybara::Minitest::Assertions.instance_methods(false) -
9
+ # exclude methods that don't have side-effects i.e. don't modify the page:
10
+ %i[
11
+ body
12
+ html
13
+ source
14
+ current_url
15
+ current_host
16
+ current_path
17
+ current_scope
18
+ status_code
19
+ response_headers
20
+ ]
21
+
22
+ def self.step_at_methods
23
+ @step_at_methods
24
+ end
25
+ def self.add_step_at_methods(*mods_or_methods)
26
+ mods_or_methods.each do |mod_or_method|
27
+ case mod_or_method
28
+ when Module
29
+ @step_at_methods +=
30
+ mod_or_method.methods(false) + mod_or_method.instance_methods(false)
31
+ when String, Symbol
32
+ @step_at_methods << mod_or_method.to_sym
33
+ else
34
+ raise "Expected Module or Array of strings/symbols, got #{mod_or_method.class}"
35
+ end
36
+ end
37
+ end
38
+
4
39
  def initialize(test_class)
5
40
  @test_class = test_class
6
41
  @source_filepath = Object.const_source_location(test_class.name).first
@@ -24,14 +59,14 @@ class Cyperful::TestParser
24
59
  end
25
60
 
26
61
  (
27
- # the children of the `class` node are either:
28
- # - a `begin` node if there's more than 1 child node
29
- # - or the one 0 or 1 child node
30
- system_test_class
31
- .children
32
- .find { |node| node.type == :begin }
33
- &.children || [system_test_class.children[2]].compact
34
- )
62
+ # the children of the `class` node are either:
63
+ # - a `begin` node if there's more than 1 child node
64
+ # - or the one 0 or 1 child node
65
+ system_test_class
66
+ .children
67
+ .find { |node| node.type == :begin }
68
+ &.children || [system_test_class.children[2]].compact
69
+ )
35
70
  .map do |node|
36
71
  # e.g. `test "my test" do ... end`
37
72
  if node.type == :block && node.children[0].type == :send &&
@@ -50,29 +85,48 @@ class Cyperful::TestParser
50
85
  end
51
86
  .compact
52
87
  .to_h do |test_method, block_node|
53
- [
54
- test_method,
55
- find_test_steps(block_node)
56
- # sanity check:
57
- .uniq { |step| step[:line] },
58
- ]
88
+ out = []
89
+ block_node.children.each { |child| find_test_steps(child, out) }
90
+
91
+ [test_method, out]
59
92
  end
60
93
  end
61
94
 
62
- private def find_test_steps(ast, out = [])
95
+ private def find_test_steps(ast, out = [], depth = 0)
63
96
  return out unless ast&.is_a?(Parser::AST::Node)
64
97
 
65
- if ast.type == :send && Cyperful.step_at_methods.include?(ast.children[1])
66
- out << {
67
- method: ast.children[1],
68
- line: ast.loc.line,
69
- column: ast.loc.column,
70
- as_string: ast.loc.expression.source,
71
- }
72
- end
98
+ if ast.type == :send
99
+ add_node(ast, out, depth)
100
+ ast.children.each { |child| find_test_steps(child, out, depth) }
101
+ elsif ast.type == :block
102
+ method, _args, child = ast.children
73
103
 
74
- ast.children.each { |child| find_test_steps(child, out) }
104
+ children = child.type == :begin ? child.children : [child]
105
+
106
+ if method.type == :send
107
+ depth += 1 if add_node(method, out, depth)
108
+ method.children.each { |child| find_test_steps(child, out, depth) }
109
+ end
110
+
111
+ children.each { |child| find_test_steps(child, out, depth) }
112
+ end
75
113
 
76
114
  out
77
115
  end
116
+
117
+ private def add_node(node, out, depth)
118
+ unless Cyperful::TestParser.step_at_methods.include?(node.children[1])
119
+ return false
120
+ end
121
+
122
+ out << {
123
+ method: node.children[1],
124
+ line: node.loc.line,
125
+ column: node.loc.column,
126
+ as_string: node.loc.expression.source,
127
+ block_depth: depth,
128
+ }
129
+
130
+ true
131
+ end
78
132
  end
data/lib/cyperful.rb CHANGED
@@ -29,23 +29,8 @@ module Cyperful
29
29
  @current&.teardown(error)
30
30
  end
31
31
 
32
- # more potential methods: https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Session
33
- @step_at_methods = [*Capybara::Session::NODE_METHODS, :visit, :refresh]
34
- def self.step_at_methods
35
- @step_at_methods
36
- end
37
32
  def self.add_step_at_methods(*mods_or_methods)
38
- mods_or_methods.each do |mod_or_method|
39
- case mod_or_method
40
- when Module
41
- @step_at_methods +=
42
- mod_or_method.methods(false) + mod_or_method.instance_methods(false)
43
- when String, Symbol
44
- @step_at_methods << mod_or_method.to_sym
45
- else
46
- raise "Expected Module or Array of strings/symbols, got #{mod_or_method.class}"
47
- end
48
- end
33
+ Cyperful::TestParser.add_step_at_methods(*mods_or_methods)
49
34
  end
50
35
  end
51
36
 
@@ -1 +1 @@
1
- *,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}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;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.pointer-events-none{pointer-events:none}.static{position:static}.absolute{position:absolute}.relative{position:relative}.left-0{left:0px}.right-0{right:0px}.right-4{right:1rem}.top-0{top:0px}.top-1{top:.25rem}.m-4{margin:1rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.ml-1{margin-left:.25rem}.mr-2{margin-right:.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.hidden{display:none}.h-10{height:2.5rem}.h-14{height:3.5rem}.h-5{height:1.25rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.h-screen{height:100vh}.max-h-16{max-height:4rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-full{width:100%}.flex-1{flex:1 1 0%}.basis-96{flex-basis:24rem}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize{resize:both}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-md{border-radius:.375rem}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity))}.bg-\[\#121b2e\]{--tw-bg-opacity: 1;background-color:rgb(18 27 46 / var(--tw-bg-opacity))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}.bg-orange-400{--tw-bg-opacity: 1;background-color:rgb(251 146 60 / var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity))}.p-2{padding:.5rem}.p-4{padding:1rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.pt-0{padding-top:0}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\[1\.25em\]{font-size:1.25em}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity))}.opacity-0{opacity:0}.opacity-50{opacity:.5}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-inner{--tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / .05);--tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@keyframes rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.Logo g{transform-origin:50% 50%;animation:rotate 5s linear infinite;animation-play-state:paused}.Logo.Logo--animating g{animation-play-state:running}.Logo g:nth-child(1){animation-duration:2s}.Logo g:nth-child(2){animation-duration:2.5s}.Logo g:nth-child(3){animation-duration:3s}.Logo g:nth-child(4){animation-duration:3.5s}.hover\:bg-blue-600:hover{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.hover\:bg-gray-300:hover{--tw-bg-opacity: 1;background-color:rgb(209 213 219 / var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity))}.hover\:bg-orange-500:hover{--tw-bg-opacity: 1;background-color:rgb(249 115 22 / var(--tw-bg-opacity))}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity))}.hover\:bg-yellow-600:hover{--tw-bg-opacity: 1;background-color:rgb(202 138 4 / var(--tw-bg-opacity))}.hover\:underline:hover{text-decoration-line:underline}.group:hover .group-hover\:block{display:block}
1
+ *,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}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;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.pointer-events-none{pointer-events:none}.static{position:static}.absolute{position:absolute}.relative{position:relative}.left-0{left:0px}.right-0{right:0px}.right-4{right:1rem}.top-0{top:0px}.top-1{top:.25rem}.m-4{margin:1rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.ml-1{margin-left:.25rem}.mr-2{margin-right:.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.hidden{display:none}.h-10{height:2.5rem}.h-14{height:3.5rem}.h-5{height:1.25rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.h-screen{height:100vh}.max-h-16{max-height:4rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-full{width:100%}.flex-1{flex:1 1 0%}.basis-96{flex-basis:24rem}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize{resize:both}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-md{border-radius:.375rem}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity))}.bg-\[\#121b2e\]{--tw-bg-opacity: 1;background-color:rgb(18 27 46 / var(--tw-bg-opacity))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}.bg-orange-400{--tw-bg-opacity: 1;background-color:rgb(251 146 60 / var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity))}.p-2{padding:.5rem}.p-4{padding:1rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.pt-0{padding-top:0}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\[1\.25em\]{font-size:1.25em}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity))}.opacity-0{opacity:0}.opacity-50{opacity:.5}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-inner{--tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / .05);--tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@keyframes rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.Logo g{transform-origin:50% 50%;animation:rotate 5s linear infinite;animation-play-state:paused}.Logo.Logo--animating g{animation-play-state:running}.Logo g:nth-child(1){animation-duration:1s}.Logo g:nth-child(2){animation-duration:1.5s}.Logo g:nth-child(3){animation-duration:3s}.Logo g:nth-child(4){animation-duration:4s}.hover\:bg-blue-600:hover{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.hover\:bg-gray-300:hover{--tw-bg-opacity: 1;background-color:rgb(209 213 219 / var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity))}.hover\:bg-orange-500:hover{--tw-bg-opacity: 1;background-color:rgb(249 115 22 / var(--tw-bg-opacity))}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity))}.hover\:bg-yellow-600:hover{--tw-bg-opacity: 1;background-color:rgb(202 138 4 / var(--tw-bg-opacity))}.hover\:underline:hover{text-decoration-line:underline}.group:hover .group-hover\:block{display:block}