cyperful 0.1.2 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c54a2f9266b145a049ddf720e90e50ec1250867ab51b51b046792ec36f57438a
4
- data.tar.gz: a6dfac785f965f733c9b5fe41ea99c2437972f1a63dbbb346dfa822f6c2f187b
3
+ metadata.gz: 61dce63e0bff592ded11835cf202b241d717acaaf99331db8f447d3d7dc2aba9
4
+ data.tar.gz: a2ec8532880c523e33ce15d377a1fc28b6832354306c784fa3dea7a38200302b
5
5
  SHA512:
6
- metadata.gz: 79c77ab5eb6c05c9eb4531964b545ad2580e2a2e8a188835ec7bc5d261087eb05603faaa9ea7fbc16509f4c3e9675c1750bdf96e23c42716e2efcbae93715fab
7
- data.tar.gz: 23bb98e1e7e6bb5f2939461ecfdf7b66bd5d04f06b449696c7ebacde7b8bf4b69396a48451fc3d3fe5e7aa5f199955bab4beaaadc6524c38c05003678998a0b7
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
@@ -301,18 +328,18 @@ class Cyperful::Driver
301
328
  @cyperful_origin = @ui_server.url_origin
302
329
 
303
330
  @ui_server.on_command do |command, params|
304
- case command
305
- when "start"
331
+ case command.to_sym
332
+ when :start
306
333
  # one of: integer (index of a step), true (pause at every step), or nil (don't pause)
307
334
  @pause_at_step = params["pause_at_step"]
308
335
 
309
336
  continue_next_step
310
- when "reset"
337
+ when :reset
311
338
  @pause_at_step = true
312
339
  @step_pausing_queue.enq(:reset)
313
- when "stop"
340
+ when :stop
314
341
  @pause_at_step = true # enable pausing
315
- when "exit"
342
+ when :exit
316
343
  @pause_at_step = true
317
344
 
318
345
  # instead of calling `exit` directly, we need to raise a Cyperful::ExitCommand error
@@ -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
 
@@ -352,13 +376,14 @@ class Cyperful::Driver
352
376
  if error
353
377
  # get the 4 lines following the first line that includes the source file
354
378
  i = nil
355
- backtrace =
356
- error.backtrace.select do |s|
357
- i ||= 0 if s.include?(@source_filepath)
358
- i += 1 if i
359
- break if i && i > 4
360
- true
361
- end
379
+ backtrace = []
380
+ error.backtrace.each do |s|
381
+ i ||= 0 if s.include?(@source_filepath)
382
+ next unless i
383
+ i += 1
384
+ backtrace << s
385
+ break if i >= 6
386
+ end
362
387
 
363
388
  warn "\n\nTest failed with error:\n#{error.message}\n#{backtrace.join("\n")}"
364
389
  end
@@ -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--animating g{transform-origin:50% 50%;animation:rotate 5s linear infinite}.Logo--animating g:nth-child(1){animation-duration:2s}.Logo--animating g:nth-child(2){animation-duration:3s}.Logo--animating g:nth-child(3){animation-duration:5s}.Logo--animating g:nth-child(4){animation-duration:7s}.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}