diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 00000000..fc936790 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 9db9da0647afe63ce71f2b065b7306e5 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/architecture.doctree b/.doctrees/architecture.doctree new file mode 100644 index 00000000..90a3016a Binary files /dev/null and b/.doctrees/architecture.doctree differ diff --git a/.doctrees/backpressure.doctree b/.doctrees/backpressure.doctree new file mode 100644 index 00000000..59223eb2 Binary files /dev/null and b/.doctrees/backpressure.doctree differ diff --git a/.doctrees/dlqs.doctree b/.doctrees/dlqs.doctree new file mode 100644 index 00000000..4efc44a2 Binary files /dev/null and b/.doctrees/dlqs.doctree differ diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle new file mode 100644 index 00000000..7fb384f5 Binary files /dev/null and b/.doctrees/environment.pickle differ diff --git a/.doctrees/getstarted.doctree b/.doctrees/getstarted.doctree new file mode 100644 index 00000000..cd267b2a Binary files /dev/null and b/.doctrees/getstarted.doctree differ diff --git a/.doctrees/index.doctree b/.doctrees/index.doctree new file mode 100644 index 00000000..2ccf356b Binary files /dev/null and b/.doctrees/index.doctree differ diff --git a/.doctrees/intro.doctree b/.doctrees/intro.doctree new file mode 100644 index 00000000..4cbdf306 Binary files /dev/null and b/.doctrees/intro.doctree differ diff --git a/.doctrees/metrics.doctree b/.doctrees/metrics.doctree new file mode 100644 index 00000000..5c89aae1 Binary files /dev/null and b/.doctrees/metrics.doctree differ diff --git a/.doctrees/offsets.doctree b/.doctrees/offsets.doctree new file mode 100644 index 00000000..3425f3b3 Binary files /dev/null and b/.doctrees/offsets.doctree differ diff --git a/.doctrees/strategies/batching.doctree b/.doctrees/strategies/batching.doctree new file mode 100644 index 00000000..88b2eba6 Binary files /dev/null and b/.doctrees/strategies/batching.doctree differ diff --git a/.doctrees/strategies/commit_offsets.doctree b/.doctrees/strategies/commit_offsets.doctree new file mode 100644 index 00000000..29db4f28 Binary files /dev/null and b/.doctrees/strategies/commit_offsets.doctree differ diff --git a/.doctrees/strategies/filter.doctree b/.doctrees/strategies/filter.doctree new file mode 100644 index 00000000..60410c79 Binary files /dev/null and b/.doctrees/strategies/filter.doctree differ diff --git a/.doctrees/strategies/healthcheck.doctree b/.doctrees/strategies/healthcheck.doctree new file mode 100644 index 00000000..cc69e6c6 Binary files /dev/null and b/.doctrees/strategies/healthcheck.doctree differ diff --git a/.doctrees/strategies/index.doctree b/.doctrees/strategies/index.doctree new file mode 100644 index 00000000..8ee7a4aa Binary files /dev/null and b/.doctrees/strategies/index.doctree differ diff --git a/.doctrees/strategies/noop.doctree b/.doctrees/strategies/noop.doctree new file mode 100644 index 00000000..d8fbffcb Binary files /dev/null and b/.doctrees/strategies/noop.doctree differ diff --git a/.doctrees/strategies/produce.doctree b/.doctrees/strategies/produce.doctree new file mode 100644 index 00000000..07bbed01 Binary files /dev/null and b/.doctrees/strategies/produce.doctree differ diff --git a/.doctrees/strategies/reduce.doctree b/.doctrees/strategies/reduce.doctree new file mode 100644 index 00000000..44e78df2 Binary files /dev/null and b/.doctrees/strategies/reduce.doctree differ diff --git a/.doctrees/strategies/run_task.doctree b/.doctrees/strategies/run_task.doctree new file mode 100644 index 00000000..8178f665 Binary files /dev/null and b/.doctrees/strategies/run_task.doctree differ diff --git a/.doctrees/strategies/run_task_in_threads.doctree b/.doctrees/strategies/run_task_in_threads.doctree new file mode 100644 index 00000000..96622b19 Binary files /dev/null and b/.doctrees/strategies/run_task_in_threads.doctree differ diff --git a/.doctrees/strategies/run_task_with_multiprocessing.doctree b/.doctrees/strategies/run_task_with_multiprocessing.doctree new file mode 100644 index 00000000..45e4f094 Binary files /dev/null and b/.doctrees/strategies/run_task_with_multiprocessing.doctree differ diff --git a/.doctrees/strategies/unfold.doctree b/.doctrees/strategies/unfold.doctree new file mode 100644 index 00000000..74086ac0 Binary files /dev/null and b/.doctrees/strategies/unfold.doctree differ diff --git a/.doctrees/what_for.doctree b/.doctrees/what_for.doctree new file mode 100644 index 00000000..5ece2d40 Binary files /dev/null and b/.doctrees/what_for.doctree differ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/_images/arroyo-banner.png b/_images/arroyo-banner.png new file mode 100644 index 00000000..90482cb6 Binary files /dev/null and b/_images/arroyo-banner.png differ diff --git a/_images/arroyo_processing.png b/_images/arroyo_processing.png new file mode 100644 index 00000000..21004dde Binary files /dev/null and b/_images/arroyo_processing.png differ diff --git a/_images/consumer_groups.png b/_images/consumer_groups.png new file mode 100644 index 00000000..09aa7440 Binary files /dev/null and b/_images/consumer_groups.png differ diff --git a/_images/kafka_producer.png b/_images/kafka_producer.png new file mode 100644 index 00000000..53a90d9d Binary files /dev/null and b/_images/kafka_producer.png differ diff --git a/_sources/architecture.rst.txt b/_sources/architecture.rst.txt new file mode 100644 index 00000000..0fedb49e --- /dev/null +++ b/_sources/architecture.rst.txt @@ -0,0 +1,120 @@ +=================== +Arroyo Architecture +=================== + +Arroyo is a set of high level abstractions to interact with Kafka. +These are meant to help the developer in writing performant consumers with +specific delivery guarantees. + +Common problems addressed by Arroyo are guaranteeing at-least-once delivery, +providing a dead letter queue abstraction, support parallel (multi-processing) +message processing, etc. + +The library is divided into three layers: the basic Kafka connectivity, the +streaming engine and the high level abstractions. + +The basic connectivity layer is a simple wrapper around the Confluent python +library, which is itself based on librdkafka. Besides some cosmetic changes, +this level provides a Fake in memory broker and consumer to make unit test quick +to run. + +The streaming engine provides an asynchronous processing interface to write +consumers. The consumer is written as a pipeline where each segment is an +asynchronous operation. The streaming engine implements the main consumer loop +and delegates the processing to the pipeline. + +On top of the streaming engine, the library provides high-level abstractions that +are common when writing Kafka consumers like: *map*, *reduce*, *filter* together +with some common messaging application patterns like the dead letter queue. + +Streaming Interface and Streaming Engine +---------------------------------------- + +A Kafka consumer is built as a pipeline where each segment processes messages in +an asynchronous way. The Streaming engine provides a message to a segment. The +segment is not supposed to execute small CPU work in a blocking way or do IO in a +non-blocking way. We generally use futures for this, and heavier CPU work in a +separate process. + +Arroyo provides an interface to implement to write a pipeline segment. +The segment interface is called *ProcessingStrategy* and is in +`this module `_. +(TODO: bring the docstrings to the docs and reference that). + +In most cases, when developing a consumer, the developer would not implement +that interface directly. A higher level abstraction would be used. + +.. figure:: _static/diagrams/arroyo_processing.png + +The main consumer loop is managed by the `stream engine `_. +These are the phases: + +* Poll from the Kafka consumer through the basic library. If a message is there + proceed or repeat. + +* Submit the message to the first *ProcessingStrategy*. This is supposed to deliver + work for the strategy to do. It is not supposed to be a blocking operation. The + strategy should return immediately. + +* Poll the strategy to execute work or to forward results to the following step + in the pipeline. Ideally all IO should be done in separate threads and heavy cpu + work should be done in separate processes so the *poll* method should check for + completed work, dispatch to the next step and return. In practice, work is executed + here in a blocking way if the overhead of offloading the work is too high. + +The *ProcessingStrategy* may decide not to take the message and instead apply back-pressure. +This is done by raising the *MessageRejected* exception. In this case, the streaming +engine pauses the consumer till the strategy is ready to accept the message. + +The *ProcessingStrategy* decides when it is time to commit a message. This is done +through a commit callback provided to the strategy when it is instantiated. + +The streaming engine orchestrates the life cycle of the *ProcessingStrategy*, thus +when it thinks it is time to shut the strategy down it would wait for all in-flight +work to be completed and then destroy the strategy. + +There are two scenarios where this can happen: + +* The consumer is being terminated. +* A rebalancing happened. A rebalancing revokes partitions and assigns new ones. + After a rebalancing is complete it is impossible to commit a message from a partition + that was revoked. In order to ensure the consumer behaves in a consistent way, + upon rebalancing, the streaming engine destroys the strategy and builds a new one. + This allows the strategy to complete all in-flight work before being terminated. + +High level strategies +----------------------- + +Most consumers follow the same few patterns, so Arroyo provides abstractions that +are based on the *ProcessingStrategy* but are simpler to implement for the common +use cases. + +Common examples are: + +* ``run task, run task in threads, run task with multiprocessing``. The run task + set of strategies are designed to be the most flexible and simple to use. They take + a function provided by the user and execute it on every message, passing the output + to the next step. The library includes synchronous and asynchronous versions depending + on the kind of concurrency required by the user. + +* ``filter, map and forward``. This type of consumer inspects a message, decides + whether to process it or discard it, transforms its content, and produces the result + on a new topic. In this case, Arroyo provides three implementations of the + *ProcessingStrategy*: *filter*, *transform*, and *produce*. The developer only needs + to wire them together and provide the map and filtering logic. + +* ``consume, apply side effects, produce``. This is a variation of the one above. + In this case, the transform operation can have side-effects like storing the content + of the message somewhere. + +* ``high throughput cpu intensive transform``. The python GIL does not allow CPU intensive + work to take advantage of parallelism. Arroyo provides an implementation of the *map* + pattern that batches messages and dispatches the work to separate processes via shared + memory. This is largely transparent to the developers. + +* ``map, reduce and store``. The reduce function is carried out by the *Collector*, which + batches messages and executes some logic with side-effects when the batch is full. + This is a typical way to write messages on a storages in batches to reduce the + round trips. + +All strategies included with Arroyo are in `the strategies module `_. diff --git a/_sources/backpressure.rst.txt b/_sources/backpressure.rst.txt new file mode 100644 index 00000000..2dcc20df --- /dev/null +++ b/_sources/backpressure.rst.txt @@ -0,0 +1,43 @@ +Backpressure +============ + +.. py:currentmodule:: arroyo.processing.strategies + +Arroyo's own processing strategies internally apply backpressure by raising +:py:class:`~abstract.MessageRejected`. Most +consumers do not require additional work to deal with backpressure correctly. + +If you want to slow down the consumer based on some external signal or +condition, you can achieve that most effectively by raising the same exception +from within a callback passed to :py:class:`~run_task.RunTask` while the +consumer is supposed to be paused + +.. code-block:: Python + + class ConsumerStrategyFactory(ProcessingStrategyFactory[KafkaPayload]): + def __init__(self): + self.is_paused = False + + def create_with_partitions( + self, + commit: Commit, + partitions: Mapping[Partition, int], + ) -> ProcessingStrategy[KafkaPayload]: + def handle_message(message: Message[KafkaPayload]) -> Message[KafkaPayload]: + if self.is_paused: + raise MessageRejected() + + print(f"MSG: {message.payload}") + return message + + return RunTask(handle_message, CommitOffsets(commit)) + +It is not recommended to apply backpressure by just ``sleep()``-ing in +:py:class:`~abstract.ProcessingStrategy.submit` (or, in this example, +``handle_message``) for more than a few milliseconds. While this definitely +pauses the consumer, it will block the main thread for too long and and prevent +things like consumer rebalancing from occuring. + +A 0.01 second sleep is applied each time :py:class:`~abstract.MessageRejected` is +raised to prevent the main thread spinning at 100% CPU. However background thread +performance may be impacted during this time. diff --git a/_sources/dlqs.rst.txt b/_sources/dlqs.rst.txt new file mode 100644 index 00000000..62562a39 --- /dev/null +++ b/_sources/dlqs.rst.txt @@ -0,0 +1,21 @@ +================== +Dead letter queues +================== + +.. warning:: + Dead letter queues should be used with caution as they break some of the ordering guarantees + otherwise offered by Arroyo and Kafka consumer code. In particular, it must be safe for the + consumer to drop a message. If replaying or later re-processing of the DLQ'ed messages is done, + it is critical that ordering is not a requirement in the relevant downstream code. + +Arroyo provides support for routing invalid messages to dead letter queues in consumers. +Dead letter queues are critical in some applications because messages are ordered in Kafka +and a single invalid message can cause a consumer to crash and every subsequent message to +not be processed. + +The dead letter queue configuration is passed to the `StreamProcessor` and, if provided, any +`InvalidMessage` raise by a strategy will be produced to the dead letter queue. + + +.. automodule:: arroyo.dlq + :members: InvalidMessage, DlqLimit, DlqPolicy, DlqProducer, KafkaDlqProducer, NoopDlqProducer diff --git a/_sources/getstarted.rst.txt b/_sources/getstarted.rst.txt new file mode 100644 index 00000000..d0ce1308 --- /dev/null +++ b/_sources/getstarted.rst.txt @@ -0,0 +1,246 @@ +=========================== +Getting started with Arroyo +=========================== + +This tutorial shows how to create a Kafka consumer with Arroyo from scratch. + +Setup +===== + +This section explains how to setup Kafka, Zookeeper and install the library + +Kafka and Zookeeper +------------------- + +In order to run an arroyo Kafka consumer you will need a working Kafka broker. +If you already have one, you can skip this step. +If you do not have a running Kafka broker, this command will install and start +a Kafka docker container. (It requires Docker to be installed). + +.. code-block:: Bash + + docker network create arroyo + + docker run --rm \ + -v zookeeper_volume:/var/lib/zookeeper \ + --env ZOOKEEPER_CLIENT_PORT=2181 \ + --name=sentry_zookeeper \ + --network=arroyo \ + -p 2181:2181 \ + confluentinc/cp-zookeeper:6.2.0 + + docker run --rm \ + -v kafka_volume:/var/lib/kafka \ + --env KAFKA_ZOOKEEPER_CONNECT=sentry_zookeeper:2181 \ + --env KAFKA_LISTENERS=INTERNAL://0.0.0.0:9093,EXTERNAL://0.0.0.0:9092 \ + --env KAFKA_ADVERTISED_LISTENERS=INTERNAL://127.0.0.1:9093,EXTERNAL://127.0.0.1:9092 \ + --env KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT \ + --env KAFKA_INTER_BROKER_LISTENER_NAME=INTERNAL \ + --env KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \ + --env CONFLUENT_SUPPORT_METRICS_ENABLE=false \ + --env KAFKA_LOG4J_LOGGERS=kafka.cluster=WARN,kafka.controller=WARN,kafka.coordinator=WARN,kafka.log=WARN,kafka.server=WARN,kafka.zookeeper=WARN,state.change.logger=WARN \ + --env KAFKA_LOG4J_ROOT_LOGLEVEL=WARN \ + --env KAFKA_TOOLS_LOG4J_LOGLEVEL=WARN \ + --name=sentry_kafka \ + --network=arroyo \ + -p 9092:9092 \ + confluentinc/cp-kafka:6.2.0 + +Now you should see Kafka and Zookeeper running with + +.. code-block:: Bash + + docker ps + +Install Kafkacat +---------------- + +This tool will be useful to produce onto and consume from topics. + +.. code-block:: Bash + + https://docs.confluent.io/platform/current/app-development/kafkacat-usage.html#kcat-formerly-kafkacat-utility + + +Development environment +----------------------- + +You will need to install the library. Most likely in a python venv. So first, create a python virtual +environment. Then you can install arroyo with this. + +.. code-block:: Bash + + pip install sentry-arroyo + +Create two Kafka topics +----------------------- + +Our example will consume from one topic and produce the same messages on another topic. So we need +two topics. + +.. code-block:: Bash + + docker exec sentry_kafka kafka-topics \ + --create \ + --topic source-topic \ + --bootstrap-server 127.0.0.1:9092 + + docker exec sentry_kafka kafka-topics \ + --create \ + --topic dest-topic \ + --bootstrap-server 127.0.0.1:9092 + +Now you should be ready to develop with Arroyo. + +Create a basic consumer +======================= + +Arroyo provides two level of abstractions when writing a consumer: the basic consumer/producer library +and the Streaming library. The first is just a thin wrapper around a librdkafka consumer/producer that +adds some features around offset management. The second provides a more abstract streaming interface +that hides details like rebalancing and the consumer lifecycle. + +Creating a basic consumer +------------------------- + +This initializes a basic consumer and consumes a message. + +.. code-block:: Python + + from arroyo.backends.kafka.configuration import ( + build_kafka_consumer_configuration, + ) + from arroyo.backends.kafka.consumer import KafkaConsumer + from arroyo.types import Topic + + TOPIC = Topic("source-topic") + + consumer = KafkaConsumer( + build_kafka_consumer_configuration( + default_config={}, + bootstrap_servers=["127.0.0.1:9092"], + auto_offset_reset="latest", + group_id="test-group", + ) + ) + + consumer.subscribe([TOPIC]) + + while True: + msg = consumer.poll(timeout=1.0) + if msg is not None: + print(f"MSG: {msg.payload}") + +Start this script and use kcat to produce a message: + +.. code-block:: Bash + + echo "MESSAGE" | kcat -P -b 127.0.0.1:9092 -t source-topic + +In a while the message should appear on the console: + +.. code-block:: Bash + + MSG: KafkaPayload(key=None, value=b'MESSAGE', headers=[]) + + +Create a streaming consumer +--------------------------- + +Add a `ProcessingStrategy` and `ProcessingStrategyFactory`. +Here we are using the `RunTask` strategy which runs a custom function over each message. + +.. code-block:: Python + + from typing import Mapping + + from arroyo.backends.kafka import KafkaPayload + from arroyo.processing.strategies import ( + CommitOffsets, + ProcessingStrategy, + ProcessingStrategyFactory, + RunTask, + ) + from arroyo.types import Commit, Message, Partition, Topic + + + def handle_message(message: Message[KafkaPayload]) -> Message[KafkaPayload]: + print(f"MSG: {message.payload}") + return message.payload + + class ConsumerStrategyFactory(ProcessingStrategyFactory[KafkaPayload]): + """ + The factory manages the lifecycle of the `ProcessingStrategy`. + A strategy is created every time new partitions are assigned to the + consumer, while it is destroyed when partitions are revoked or the + consumer is closed + """ + def create_with_partitions( + self, + commit: Commit, + partitions: Mapping[Partition, int], + ) -> ProcessingStrategy[KafkaPayload]: + return RunTask(handle_message, CommitOffsets(commit)) + +The code above is orchestrated by the Arroyo runtime called `StreamProcessor`. + +.. code-block:: Python + + from arroyo.processing import StreamProcessor + from arroyo.commit import ONCE_PER_SECOND + + processor = StreamProcessor( + consumer=consumer, + topic=TOPIC, + processor_factory=ConsumerStrategyFactory(), + commit_policy=ONCE_PER_SECOND, + ) + + processor.run() + +The main consumer loop is managed by the `StreamProcessor` no need to periodically poll the +consumer. The `ConsumerStrategy` works by inversion of control. + +Add some useful logic +--------------------- + +Now we will chain the `Produce` strategy to produce messages on a second topic after the message is logged + +.. code-block:: Python + + from arroyo.backends.kafka import KafkaProducer + from arroyo.backends.kafka.configuration import build_kafka_configuration + from arroyo.processing.strategies import Produce + + class ConsumerStrategyFactory(ProcessingStrategyFactory[KafkaPayload]): + """ + The factory manages the lifecycle of the `ProcessingStrategy`. + A strategy is created every time new partitions are assigned to the + consumer, while it is destroyed when partitions are revoked or the + consumer is closed + """ + def create_with_partitions( + self, + commit: Commit, + partitions: Mapping[Partition, int], + ) -> ProcessingStrategy[KafkaPayload]: + producer = KafkaProducer( + build_kafka_configuration( + default_config={}, + bootstrap_servers=BOOTSTRAP_SERVERS, + ) + ) + + return RunTask( + handle_message, + Produce(producer, Topic("dest-topic"), CommitOffsets(commit)) + ) + +The message is first passed to the `RunTask` strategy which simply logs the message and submits +the output to the next step. The `Produce` strategy produces the message asynchronously. Once +the message is produced, the `CommitOffsets` strategy commits the offset of the message. + +Further examples +================ + +Find some complete `examples of usage `_. diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 00000000..fb702775 --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,16 @@ +.. include:: intro.rst + +Contents: +--------- + +.. toctree:: + :maxdepth: 2 + + what_for + getstarted + architecture + strategies/index + offsets + dlqs + backpressure + metrics diff --git a/_sources/intro.rst.txt b/_sources/intro.rst.txt new file mode 100644 index 00000000..72b0d4e2 --- /dev/null +++ b/_sources/intro.rst.txt @@ -0,0 +1,15 @@ +.. image:: _static/arroyo-banner.png + :width: 583 + :height: 95 + +Arroyo is a library to build streaming applications that consume from +and produce to Kafka. + +It relies on the `confluent_kafka` python library, which itself relies +on `librdkafka`. + +Arroyo provides mainly three functionalities: + +* A set of abstractions inspired to common messaging applications patterns. +* Some abstractions to simplify offset management and rebalancing. +* An in memory broker abstraction to simplify writing unit tests. diff --git a/_sources/metrics.rst.txt b/_sources/metrics.rst.txt new file mode 100644 index 00000000..c005c740 --- /dev/null +++ b/_sources/metrics.rst.txt @@ -0,0 +1,63 @@ +================== +Metrics +================== + + +Arroyo consumers and strategies attempt to auto instrument some metrics that most people find useful +to understand the behavior and performance of their consumers. These metrics are typically sampled or +buffered as appropriate and flushed periodically (often once per second). + +In order to use these metrics, you must configure a metrics backend that conforms to the metrics protocol +before creating your consumer. + +This can be done like so: + +.. code:: python + + from arroyo.utils.metrics import Metrics, MetricName + + class MyMetrics(Metrics): + def increment( + self, name: MetricName, value: Union[int, float] = 1, tags: Optional[Tags] = None + ) -> None: + # Increment a counter by the given value. + record_incr(name, value, tags) + + def gauge( + self, name: MetricName, value: Union[int, float], tags: Optional[Tags] = None + ) -> None: + # Sets a gauge metric to the given value. + record_gauge(name, value, tags) + + def timing( + self, name: MetricName, value: Union[int, float], tags: Optional[Tags] = None + ) -> None: + # Emit a timing metric with the given value. + record_timing(name, value, tags) + + metrics_backend = MyMetrics() + + configure_metrics(metrics_backend) + + +Available Metrics +==================== + +.. literalinclude:: ../../arroyo/utils/metric_defs.py + +For convenience Arroyo includes a machine readable version which can be loaded like: + +.. code:: python + + import importlib.resources + import json + + with importlib.resources.files("arroyo.utils").joinpath("metricDefs.json").open() as f: + metric_defs = json.load(f) + +API +======= + +.. automodule:: arroyo.utils.metrics + :members: + :undoc-members: diff --git a/_sources/offsets.rst.txt b/_sources/offsets.rst.txt new file mode 100644 index 00000000..6316e653 --- /dev/null +++ b/_sources/offsets.rst.txt @@ -0,0 +1,37 @@ +================== +Committing offsets +================== + +Arroyo does not auto commit offsets. It is up to you to manually commit offsets when processing for that +message is completed. + +The commit callback will be passed to processing strategy via `ProcessingStrategyFactory.create_with_partitions`. +You should pass this to the strategy and have your strategy call this commit function once the rest of the message +processing has been done. + +The offset to be committed in Kafka is always the next offset to be consumed from, i.e. message's offset + 1. +In Arroyo, this means you should commit `Message.next_offset` and never `Message.offset` when done processing +that message. Arroyo exposes `Message.position_to_commit` to make this easier. + +It is not safe to commit every offset in a high throughput consumer as this will add a lot of load to the system. +Commits should generally be throttled. `CommitPolicy` is the Arroyo way of specifying commit frequency. A `CommitPolicy` +must be passed to the stream processor, which allows specifying a minimum commit frequency (or messages between commits). +Commit throttling can be skipped when needed (i.e. during consumer shutdown) by passing `force=True` to the commit callback. +If you are not sure how often to commit, `ONCE_PER_SECOND` is a reasonable option. + +The easiest way is to use the `CommitOffsets` strategy as the last step in a chain of processing strategies to commit offsets. + +.. code-block:: Python + + class MyConsumerFactoryFactory(ProcessingStrategyFactory[KafkaPayload]): + def create_with_partitions( + self, + commit: Commit, + partitions: Mapping[Partition, int], + ) -> ProcessingStrategy[KafkaPayload]: + def my_processing_function(message: Message[KafkaPayload]) -> None: + # do something (synchronous) with the message + do_something() + + + return RunTask(my_processing_function, CommitOffsets(commit)) diff --git a/_sources/strategies/batching.rst.txt b/_sources/strategies/batching.rst.txt new file mode 100644 index 00000000..540d7d8a --- /dev/null +++ b/_sources/strategies/batching.rst.txt @@ -0,0 +1,10 @@ +Batch and Unbatch +----------------------------- + +Accumulate messages into a batch and pass to the next step. +The batch and unbatch strategies are based on reduce and unfold. +Use reduce/unfold instead if you want to provide custom +accumulator/generator functions. + +.. automodule:: arroyo.processing.strategies.batching + :members: diff --git a/_sources/strategies/commit_offsets.rst.txt b/_sources/strategies/commit_offsets.rst.txt new file mode 100644 index 00000000..64d3504c --- /dev/null +++ b/_sources/strategies/commit_offsets.rst.txt @@ -0,0 +1,8 @@ +Commit offsets +----------------------------- + +Should be used as the last strategy in the chain, to ensure +that offsets are only committed once all processing is complete. + +.. automodule:: arroyo.processing.strategies.commit + :members: diff --git a/_sources/strategies/filter.rst.txt b/_sources/strategies/filter.rst.txt new file mode 100644 index 00000000..22cba221 --- /dev/null +++ b/_sources/strategies/filter.rst.txt @@ -0,0 +1,5 @@ +Filter +----------------------------- + +.. automodule:: arroyo.processing.strategies.filter + :members: diff --git a/_sources/strategies/healthcheck.rst.txt b/_sources/strategies/healthcheck.rst.txt new file mode 100644 index 00000000..89fe7a3c --- /dev/null +++ b/_sources/strategies/healthcheck.rst.txt @@ -0,0 +1,74 @@ +Healthchecks +============ + +If your code blocks for too long in the main thread, the consumer can turn +unhealthy. + +Kafka has a setting called ``max.poll.interval.ms`` for this that tells Kafka +to kick the consumer out of the broker after this many milliseconds of not polling. + +You can pass this option into :py:class:`arroyo.backends.kafka.consumer.KafkaConsumer` like so: + +.. code-block:: Python + + consumer = KafkaConsumer( + { + "max.poll.interval.ms": 300000, # default 5 minutes + } + ) + +However, this will not shut down the consumer, it will just keep running doing +nothing (because it is blocked in the main thread). You want a pod-level +healthcheck as well. + +Arroyo supports touching a file repeatedly from the main thread to indicate +health. Start your pipeline with the +:py:class:`arroyo.processing.strategies.healthcheck.Healthcheck` strategy. + +.. code-block:: Python + + def handle_message(message: Message[KafkaPayload]) -> Message[KafkaPayload]: + ... + return message + + class ConsumerStrategyFactory(ProcessingStrategyFactory[KafkaPayload]): + def __init__(self): + self.is_paused = False + + def create_with_partitions( + self, + commit: Commit, + partitions: Mapping[Partition, int], + ) -> ProcessingStrategy[KafkaPayload]: + step = RunTask(handle_message, CommitOffsets(commit)) + return Healthcheck("/tmp/health.txt", step) + +The Kubernetes `liveness +`_ +command would look like: + +.. code-block:: YAML + + apiVersion: v1 + kind: Pod + metadata: + labels: + test: liveness + name: liveness-exec + spec: + containers: + - name: liveness + image: registry.k8s.io/busybox + args: + - bin/my_arroyo_consumer + livenessProbe: + exec: + command: + - rm + - /tmp/health.txt + initialDelaySeconds: 5 + periodSeconds: 320 # should be higher than max.poll.interval.ms + + +.. automodule:: arroyo.processing.strategies.healthcheck + :members: diff --git a/_sources/strategies/index.rst.txt b/_sources/strategies/index.rst.txt new file mode 100644 index 00000000..60d75e1a --- /dev/null +++ b/_sources/strategies/index.rst.txt @@ -0,0 +1,39 @@ +Processing Strategies +===================== + +The processing strategies are the components to be wired together to +build a consumer. + +Strategy interface +------------------------- + +We normally don't recommend writing your own strategy, and encourage you to use +built-in ones such as "reduce" or "run task" to plug in your application logic. +Nevertheless, all arroyo strategies are written against the following interface: + +.. automodule:: arroyo.processing.strategies.abstract + :members: + :undoc-members: + :show-inheritance: + +Messages +------------ + +.. automodule:: arroyo.types + :members: + :undoc-members: + +.. toctree:: + :hidden: + + filter + reduce + unfold + batching + run_task + run_task_in_threads + run_task_with_multiprocessing + produce + commit_offsets + noop + healthcheck diff --git a/_sources/strategies/noop.rst.txt b/_sources/strategies/noop.rst.txt new file mode 100644 index 00000000..5a2dc622 --- /dev/null +++ b/_sources/strategies/noop.rst.txt @@ -0,0 +1,5 @@ +Noop +----------------------------- + +.. automodule:: arroyo.processing.strategies.noop + :members: diff --git a/_sources/strategies/produce.rst.txt b/_sources/strategies/produce.rst.txt new file mode 100644 index 00000000..d40c3ba8 --- /dev/null +++ b/_sources/strategies/produce.rst.txt @@ -0,0 +1,5 @@ +Produce +----------------------------- + +.. automodule:: arroyo.processing.strategies.produce + :members: diff --git a/_sources/strategies/reduce.rst.txt b/_sources/strategies/reduce.rst.txt new file mode 100644 index 00000000..41be2365 --- /dev/null +++ b/_sources/strategies/reduce.rst.txt @@ -0,0 +1,7 @@ +Reduce (Fold) +----------------------------- + +Accumulate messages based on a custom accumulator function + +.. automodule:: arroyo.processing.strategies.reduce + :members: diff --git a/_sources/strategies/run_task.rst.txt b/_sources/strategies/run_task.rst.txt new file mode 100644 index 00000000..bce67b48 --- /dev/null +++ b/_sources/strategies/run_task.rst.txt @@ -0,0 +1,5 @@ +Run Task +----------------------------- + +.. automodule:: arroyo.processing.strategies.run_task + :members: diff --git a/_sources/strategies/run_task_in_threads.rst.txt b/_sources/strategies/run_task_in_threads.rst.txt new file mode 100644 index 00000000..84ea0c08 --- /dev/null +++ b/_sources/strategies/run_task_in_threads.rst.txt @@ -0,0 +1,5 @@ +Run Task in Threads +----------------------------- + +.. automodule:: arroyo.processing.strategies.run_task_in_threads + :members: diff --git a/_sources/strategies/run_task_with_multiprocessing.rst.txt b/_sources/strategies/run_task_with_multiprocessing.rst.txt new file mode 100644 index 00000000..1855dbbd --- /dev/null +++ b/_sources/strategies/run_task_with_multiprocessing.rst.txt @@ -0,0 +1,5 @@ +Run Task with Multiprocessing +----------------------------- + +.. automodule:: arroyo.processing.strategies.run_task_with_multiprocessing + :members: diff --git a/_sources/strategies/unfold.rst.txt b/_sources/strategies/unfold.rst.txt new file mode 100644 index 00000000..113dc3b7 --- /dev/null +++ b/_sources/strategies/unfold.rst.txt @@ -0,0 +1,7 @@ +Unfold +----------------------------- + +Generates a sequence of messages from a single message based on a custom generator function + +.. automodule:: arroyo.processing.strategies.unfold + :members: diff --git a/_sources/what_for.rst.txt b/_sources/what_for.rst.txt new file mode 100644 index 00000000..7e2d04fd --- /dev/null +++ b/_sources/what_for.rst.txt @@ -0,0 +1,276 @@ +What is Arroyo for? +=================== + +Arroyo is a library for writing high-throughput, testable kafka +consumers and producers. This document attempts to outline the +intricacies of writing such consumers. + +Goals +----- + +1. Make it easy to build consumers that provide delivery guarantees +2. Make it easy to write high-throughput kafka consumers +3. Make it easy to write reliable, high throughput kafka producers +4. Abstract away rebalancing such that users of the library do not have + to worry about it +5. Make it possible to test kafka consumer logic without having to + reproduce the entire kafka environment +6. Provide a way for the application logic to signal backpressure + +Why Simple Doesn’t Cut It +------------------------- + +When visualizing event-driven architecture, Kafka is viewed as an +abstract queue with groups of producers pushing to it, and consumers +consuming from it (as in the diagram below). + +.. mermaid:: + + graph TD + Producer --> Kafka_Topic + Kafka_Topic --> Consumer + Consumer --> Destination + +A more accurate model is that kafka is like a log file which is +persistent and there are offsets of the file that different consumers +have read or not read. + +The most simple kafka consumer looks something like this: + +.. code:: python + + from confluent_kafka import Consumer + + conf = { + "bootstrap.servers": "localhost:9092", + "group.id": "my_group", + "auto.offset.reset": "latest", + } + + consumer = Consumer(conf) + consumer.subscribe(["my_topic"]) + + while True: + message = consumer.poll() + send_to_destination(process_message(message)) + +This simple consumer would not satisfy the goals mentioned at the top of +this page. The following subsections will explain why + +Providing delivery guarantees +----------------------------- + +By default, a consumer in the ``confluent_kafka`` library will +auto-commit on poll. To understand what it means to commit to a kafka +topic, see the Appendix. This can lead to the following issue: + +.. code:: python + + # get message from kafka, commit immediately + message = consumer.poll() + # ❗❗❗ throws exception due to a network issue + send_to_destination(process_message(message)) + # this message is now lost and we're on to the next one + +This can be fixed by only committing after we know that the message has +reached its destination in the following way: + +.. code:: python + + # add this value to the config: + "enable.auto.commit": "false" + # ------- + message = consumer.poll(timeout=0) + send_to_destination(process_message(message)) + consumer.commit(message.offset()) + +High Throughput +--------------- + +The previous section has allowed us to not commit messages that are not +processed however committing every message severely hurts throughput. +Every call to commit is a network operation, it also makes the broker +persist and replicate the information. If we can reduce the number of +commit calls, our throughput can be much higher. And so we commit in +batches + +.. code:: python + + # this code is purely descriptive. + # We have to commit to each partition separately + # but that code is not helpful for this example + message = consumer.poll(timeout=0) + batch.append(process_message(message)) + if len(batch) == batch_size: + consumer.commit(offsets=[m.offset() for m in batch]) + +This will get us faster throughput however we are currently hand-waving +away how we send the message to its destination + +Reliable High Throughput Batched Producers +------------------------------------------ + +Producing to Kafka reliably and at high throughput is not a simple +operation. Here is how a simple Kafka Producer looks in code: + +.. code:: python + + from confluent_kafka import Producer + + conf = { + "bootstrap.servers": "localhost:9092", + } + producer = Producer(conf) + def send_to_destination(message): + # ❗ This does not do what it says + # it writes to a buffer + producer.produce("destination_topic", message) + # this will actually block until the messages are produced + # calling this after produce every time is very expensive, + # how often we flush has high impacts on the producer throughput + producer.flush() + + +At a high level, the producer is actually buffering the messages +produced to the topic + +.. image:: _static/diagrams/kafka_producer.png + +A kafka producer writes to an internal buffer. This batches the IO +(good) but you don’t know when it will ever make it to the +destination + +In order to allow for reliability of transmission, the +``confluent_kafka`` library provides `a callback to +produce `__ +like so + +.. code:: python + + def delivery_callback(error, message): + # do something here to make sure your message is in the state + # you want it to be + + producer.produce("destination_topic", message, on_delivery=delivery_callback) + +Dealing With Rebalancing +------------------------ + +What is Rebalancing +~~~~~~~~~~~~~~~~~~~ + +A kafka topic is divided into n partitions, each partition can be +consumed by exactly one consumer per `consumer +group `__. A consumer can +consume multiple partitions + +.. figure:: _static/diagrams/consumer_groups.png + +When Rebalancing Can Happen +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Rebalancing can happen due to: + +- An addition or removal of a consumer to a consumer group + + - (Every deploy does this) + +- A rebalance being kicked off manually +- A consumer pod dies and now its partition needs to be re-assigned +- Whenever the broker decides it’s a good idea (it can happen at any + time) +- TODO: More things? + +How Rebalancing Affects a Consumer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Rebalancing is annoying to handle for a consumer that processes batches, +imagine the following scenario: + +.. mermaid:: + + sequenceDiagram + Broker->>Consumer: message + activate Consumer + note right of Consumer: start building batch + Broker->>Consumer: message + Broker->>Consumer: Revoke Partition + deactivate Consumer + Consumer->>Broker: commit batch + note left of Broker: Received commit from revoked Consumer! + +Once a partition is revoked for a consumer, it cannot commit to it. This +is bad news for the batch that the consumer has built up. Each consumer +has different requirements but a decision has to be made as to whether +to flush the batch or to discard its work and let the next consumer +assigned to this partition pick it up. The rebalancing behavior can be +customized by providing an ``on_revoke`` callback to the consumer when +subscribing. + +.. code:: python + + from confluent_kafka import Consumer + + conf = { + "bootstrap.servers": "localhost:9092", + "group.id": "my_group", + "auto.offset.reset": "latest", + } + + def flush_current_batch(consumer, partitions): + # flush the current batch + pass + + consumer = Consumer(conf) + consumer.subscribe(["my_topic"], on_revoke=flush_current_batch) + +librdkafka’s Callback Hell +-------------------------- + +librdkafka uses callbacks as a core mechanic for control flow. A few +such examples have been mentioned in this document already. What is not +clear however, is that **callbacks are only called when ``poll`` is +called** + +This means that: + +- this line could possibly do a lot of work: + +.. code:: python + + # any scheduled callbacks will run within this call + message = consumer.poll() + +- No callbacks will be invoked until the consumer or producer call + ``poll`` again (for their respective callbacks) +- ``poll`` has to be called periodically on a consumer otherwise the + broker will kick the consumer out of the consumer group + + - The handling of that revocation won’t happen until ``poll`` is + called + +Conclusion +---------- + +There are many intricacies and gotchas to writing high performant, +reliable kafka consumers. This document does not outline all of them but +all of what is outlined here should be kept in mind when designing any +kafka consumer library. + +Appendix +-------- + +Committing to a Kafka Topic +--------------------------- + +A consumer comitting to a topic signals to the broker that this message +has been processed. When ``poll`` is called next by that consumer, it +will return the next message. + +`API +Doc `__ + +What is a Kafka Consumer Group +------------------------------ + +https://www.educba.com/kafka-consumer-group/ diff --git a/_static/arroyo-banner.png b/_static/arroyo-banner.png new file mode 100644 index 00000000..90482cb6 Binary files /dev/null and b/_static/arroyo-banner.png differ diff --git a/_static/arroyo-logo.png b/_static/arroyo-logo.png new file mode 100644 index 00000000..8c795208 Binary files /dev/null and b/_static/arroyo-logo.png differ diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 00000000..cfc60b86 --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,921 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/custom.css b/_static/custom.css new file mode 100644 index 00000000..a738cff4 --- /dev/null +++ b/_static/custom.css @@ -0,0 +1,10 @@ +/* fix mermaid diagrams in dark mode of shibuya theme we use */ + +html.dark .mermaid path { + stroke: #fff !important; +} + +html.dark .mermaid .marker { + stroke: #fff !important; + fill: #fff !important; +} diff --git a/_static/diagrams/arroyo_processing.png b/_static/diagrams/arroyo_processing.png new file mode 100644 index 00000000..21004dde Binary files /dev/null and b/_static/diagrams/arroyo_processing.png differ diff --git a/_static/diagrams/consumer_groups.png b/_static/diagrams/consumer_groups.png new file mode 100644 index 00000000..09aa7440 Binary files /dev/null and b/_static/diagrams/consumer_groups.png differ diff --git a/_static/diagrams/kafka_producer.png b/_static/diagrams/kafka_producer.png new file mode 100644 index 00000000..53a90d9d Binary files /dev/null and b/_static/diagrams/kafka_producer.png differ diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 00000000..d06a71d7 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 00000000..b57ae3b8 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,14 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/language_data.js b/_static/language_data.js new file mode 100644 index 00000000..250f5665 --- /dev/null +++ b/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, is available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 00000000..d96755fd Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 00000000..7107cec9 Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/print.css b/_static/print.css new file mode 100644 index 00000000..bd88769f --- /dev/null +++ b/_static/print.css @@ -0,0 +1,42 @@ +.sy-head { + position: static; + border-bottom: 1px solid var(--sy-c-divider); +} + +.sy-head-inner { + padding: 0; +} + +.sy-breadcrumbs { + display: none; +} + +h1, h2, h3, h4, h5, h6 { + page-break-inside: avoid; + page-break-after: avoid; +} + +.code-block-caption, +pre, code { + page-break-inside: avoid; + white-space: pre-wrap; + + -webkit-print-color-adjust: exact; +} + +.yue a.headerlink { + display: none; +} + +.highlight .linenos { + box-shadow: none; +} + +.admonition, +.sd-sphinx-override { + -webkit-print-color-adjust: exact; +} + +.sd-card { + page-break-inside: avoid; +} diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 00000000..449a846d --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1 @@ +:root{--syntax-pre-bg: var(--accent-a2);--syntax-cap-bg: var(--accent-a3);--syntax-highlight-bg: var(--accent-a3);--syntax-linenos-divider: var(--gray-a6);--syntax-light-text: #24292f;--syntax-light-meta: #807c87;--syntax-light-comment: #6e7781;--syntax-light-constant: #0550ae;--syntax-light-entity: #268bd2;--syntax-light-property: #8250df;--syntax-light-definition: #24292f;--syntax-light-tag: #085;--syntax-light-builtin: #b58900;--syntax-light-keyword: #cf222e;--syntax-light-exception: #e6212e;--syntax-light-string: #0a3069;--syntax-light-regexp: #e40;--syntax-light-variable: #a4480f;--syntax-light-invalid-illegal-text: #f6f8fa;--syntax-light-invalid-illegal-bg: #82071e;--syntax-light-markup-heading: #0550ae;--syntax-light-markup-italic: #24292f;--syntax-light-markup-bold: #24292f;--syntax-light-markup-deleted-text: #82071e;--syntax-light-markup-deleted-bg: #FFEBE9;--syntax-light-markup-inserted-text: #116329;--syntax-light-markup-inserted-bg: #dafbe1;--syntax-light-markup-changed-text: #953800;--syntax-light-markup-changed-bg: #ffd8b5;--syntax-light-markup-ignored-text: #eaeef2;--syntax-light-markup-ignored-bg: #0550ae;--syntax-light-meta-diff-range: #8250df;--syntax-light-special-bg: #dccafa;--syntax-dark-text: #c9d1d9;--syntax-dark-meta: #6e7781;--syntax-dark-comment: #8b949e;--syntax-dark-constant: #79c0ff;--syntax-dark-entity: #47b0fa;--syntax-dark-property: #d2a8ff;--syntax-dark-definition: #c9d1d9;--syntax-dark-tag: #7ee787;--syntax-dark-builtin: #ffd34c;--syntax-dark-keyword: #ff7b72;--syntax-dark-exception: #da473c;--syntax-dark-string: #a5d6ff;--syntax-dark-regexp: #ef954e;--syntax-dark-variable: #ffa657;--syntax-dark-invalid-illegal-text: #f0f6fc;--syntax-dark-invalid-illegal-bg: #8e1519;--syntax-dark-markup-heading: #1f6feb;--syntax-dark-markup-italic: #c9d1d9;--syntax-dark-markup-bold: #c9d1d9;--syntax-dark-markup-deleted-text: #ffdcd7;--syntax-dark-markup-deleted-bg: #67060c;--syntax-dark-markup-inserted-text: #aff5b4;--syntax-dark-markup-inserted-bg: #033a16;--syntax-dark-markup-changed-text: #ffdfb6;--syntax-dark-markup-changed-bg: #5a1e02;--syntax-dark-markup-ignored-text: #c9d1d9;--syntax-dark-markup-ignored-bg: #1158c7;--syntax-dark-meta-diff-range: #d2a8ff;--syntax-dark-special-bg: #4f425d}:root,html.light{--syntax-text: var(--syntax-light-text);--syntax-meta: var(--syntax-light-meta);--syntax-comment: var(--syntax-light-comment);--syntax-constant: var(--syntax-light-constant);--syntax-entity: var(--syntax-light-entity);--syntax-property: var(--syntax-light-property);--syntax-definition: var(--syntax-light-definition);--syntax-tag: var(--syntax-light-tag);--syntax-builtin: var(--syntax-light-builtin);--syntax-keyword: var(--syntax-light-keyword);--syntax-exception: var(--syntax-light-exception);--syntax-string: var(--syntax-light-string);--syntax-regexp: var(--syntax-light-regexp);--syntax-variable: var(--syntax-light-variable);--syntax-invalid-illegal-text: var(--syntax-light-invalid-illegal-text);--syntax-invalid-illegal-bg: var(--syntax-light-invalid-illegal-bg);--syntax-markup-heading: var(--syntax-light-markup-heading);--syntax-markup-italic: var(--syntax-light-markup-italic);--syntax-markup-bold: var(--syntax-light-markup-bold);--syntax-markup-deleted-text: var(--syntax-light-markup-deleted-text);--syntax-markup-deleted-bg: var(--syntax-light-markup-deleted-bg);--syntax-markup-inserted-text: var(--syntax-light-markup-inserted-text);--syntax-markup-inserted-bg: var(--syntax-light-markup-inserted-bg);--syntax-markup-changed-text: var(--syntax-light-markup-changed-text);--syntax-markup-changed-bg: var(--syntax-light-markup-changed-bg);--syntax-markup-ignored-text: var(--syntax-light-markup-ignored-text);--syntax-markup-ignored-bg: var(--syntax-light-markup-ignored-bg);--syntax-meta-diff-range: var(--syntax-light-meta-diff-range);--syntax-special-bg: var(--syntax-light-special-bg)}@media (prefers-color-scheme: dark){:root{--syntax-text: var(--syntax-dark-text);--syntax-meta: var(--syntax-dark-meta);--syntax-comment: var(--syntax-dark-comment);--syntax-constant: var(--syntax-dark-constant);--syntax-entity: var(--syntax-dark-entity);--syntax-property: var(--syntax-dark-property);--syntax-definition: var(--syntax-dark-definition);--syntax-tag: var(--syntax-dark-tag);--syntax-builtin: var(--syntax-dark-builtin);--syntax-keyword: var(--syntax-dark-keyword);--syntax-exception: var(--syntax-dark-exception);--syntax-string: var(--syntax-dark-string);--syntax-regexp: var(--syntax-dark-regexp);--syntax-variable: var(--syntax-dark-variable);--syntax-invalid-illegal-text: var(--syntax-dark-invalid-illegal-text);--syntax-invalid-illegal-bg: var(--syntax-dark-invalid-illegal-bg);--syntax-markup-heading: var(--syntax-dark-markup-heading);--syntax-markup-italic: var(--syntax-dark-markup-italic);--syntax-markup-bold: var(--syntax-dark-markup-bold);--syntax-markup-deleted-text: var(--syntax-dark-markup-deleted-text);--syntax-markup-deleted-bg: var(--syntax-dark-markup-deleted-bg);--syntax-markup-inserted-text: var(--syntax-dark-markup-inserted-text);--syntax-markup-inserted-bg: var(--syntax-dark-markup-inserted-bg);--syntax-markup-changed-text: var(--syntax-dark-markup-changed-text);--syntax-markup-changed-bg: var(--syntax-dark-markup-changed-bg);--syntax-markup-ignored-text: var(--syntax-dark-markup-ignored-text);--syntax-markup-ignored-bg: var(--syntax-dark-markup-ignored-bg);--syntax-meta-diff-range: var(--syntax-dark-meta-diff-range);--syntax-special-bg: var(--syntax-dark-special-bg)}}html.dark{--syntax-text: var(--syntax-dark-text);--syntax-meta: var(--syntax-dark-meta);--syntax-comment: var(--syntax-dark-comment);--syntax-constant: var(--syntax-dark-constant);--syntax-entity: var(--syntax-dark-entity);--syntax-property: var(--syntax-dark-property);--syntax-definition: var(--syntax-dark-definition);--syntax-tag: var(--syntax-dark-tag);--syntax-builtin: var(--syntax-dark-builtin);--syntax-keyword: var(--syntax-dark-keyword);--syntax-exception: var(--syntax-dark-exception);--syntax-string: var(--syntax-dark-string);--syntax-regexp: var(--syntax-dark-regexp);--syntax-variable: var(--syntax-dark-variable);--syntax-invalid-illegal-text: var(--syntax-dark-invalid-illegal-text);--syntax-invalid-illegal-bg: var(--syntax-dark-invalid-illegal-bg);--syntax-markup-heading: var(--syntax-dark-markup-heading);--syntax-markup-italic: var(--syntax-dark-markup-italic);--syntax-markup-bold: var(--syntax-dark-markup-bold);--syntax-markup-deleted-text: var(--syntax-dark-markup-deleted-text);--syntax-markup-deleted-bg: var(--syntax-dark-markup-deleted-bg);--syntax-markup-inserted-text: var(--syntax-dark-markup-inserted-text);--syntax-markup-inserted-bg: var(--syntax-dark-markup-inserted-bg);--syntax-markup-changed-text: var(--syntax-dark-markup-changed-text);--syntax-markup-changed-bg: var(--syntax-dark-markup-changed-bg);--syntax-markup-ignored-text: var(--syntax-dark-markup-ignored-text);--syntax-markup-ignored-bg: var(--syntax-dark-markup-ignored-bg);--syntax-meta-diff-range: var(--syntax-dark-meta-diff-range);--syntax-special-bg: var(--syntax-dark-special-bg)}.light .dark-code{--syntax-pre-bg: var(--black-a12);--syntax-cap-bg: #1C2024;--syntax-highlight-bg: var(--white-a2);--syntax-linenos-divider: var(--white-a4);--syntax-text: var(--syntax-dark-text);--syntax-meta: var(--syntax-dark-meta);--syntax-comment: var(--syntax-dark-comment);--syntax-constant: var(--syntax-dark-constant);--syntax-entity: var(--syntax-dark-entity);--syntax-property: var(--syntax-dark-property);--syntax-definition: var(--syntax-dark-definition);--syntax-tag: var(--syntax-dark-tag);--syntax-builtin: var(--syntax-dark-builtin);--syntax-keyword: var(--syntax-dark-keyword);--syntax-exception: var(--syntax-dark-exception);--syntax-string: var(--syntax-dark-string);--syntax-regexp: var(--syntax-dark-regexp);--syntax-variable: var(--syntax-dark-variable);--syntax-invalid-illegal-text: var(--syntax-dark-invalid-illegal-text);--syntax-invalid-illegal-bg: var(--syntax-dark-invalid-illegal-bg);--syntax-markup-heading: var(--syntax-dark-markup-heading);--syntax-markup-italic: var(--syntax-dark-markup-italic);--syntax-markup-bold: var(--syntax-dark-markup-bold);--syntax-markup-deleted-text: var(--syntax-dark-markup-deleted-text);--syntax-markup-deleted-bg: var(--syntax-dark-markup-deleted-bg);--syntax-markup-inserted-text: var(--syntax-dark-markup-inserted-text);--syntax-markup-inserted-bg: var(--syntax-dark-markup-inserted-bg);--syntax-markup-changed-text: var(--syntax-dark-markup-changed-text);--syntax-markup-changed-bg: var(--syntax-dark-markup-changed-bg);--syntax-markup-ignored-text: var(--syntax-dark-markup-ignored-text);--syntax-markup-ignored-bg: var(--syntax-dark-markup-ignored-bg);--syntax-meta-diff-range: var(--syntax-dark-meta-diff-range);--syntax-special-bg: var(--syntax-dark-special-bg)}html.light .sd-tab-content,html.light .jupyter_container .cell_output{--syntax-pre-bg: var(--color-surface-accent);--syntax-cap-bg: var(--accent-3);--syntax-highlight-bg: var(--accent-a3);--syntax-text: var(--syntax-light-text);--syntax-meta: var(--syntax-light-meta);--syntax-comment: var(--syntax-light-comment);--syntax-constant: var(--syntax-light-constant);--syntax-entity: var(--syntax-light-entity);--syntax-property: var(--syntax-light-property);--syntax-definition: var(--syntax-light-definition);--syntax-tag: var(--syntax-light-tag);--syntax-builtin: var(--syntax-light-builtin);--syntax-keyword: var(--syntax-light-keyword);--syntax-exception: var(--syntax-light-exception);--syntax-string: var(--syntax-light-string);--syntax-regexp: var(--syntax-light-regexp);--syntax-variable: var(--syntax-light-variable);--syntax-invalid-illegal-text: var(--syntax-light-invalid-illegal-text);--syntax-invalid-illegal-bg: var(--syntax-light-invalid-illegal-bg);--syntax-markup-heading: var(--syntax-light-markup-heading);--syntax-markup-italic: var(--syntax-light-markup-italic);--syntax-markup-bold: var(--syntax-light-markup-bold);--syntax-markup-deleted-text: var(--syntax-light-markup-deleted-text);--syntax-markup-deleted-bg: var(--syntax-light-markup-deleted-bg);--syntax-markup-inserted-text: var(--syntax-light-markup-inserted-text);--syntax-markup-inserted-bg: var(--syntax-light-markup-inserted-bg);--syntax-markup-changed-text: var(--syntax-light-markup-changed-text);--syntax-markup-changed-bg: var(--syntax-light-markup-changed-bg);--syntax-markup-ignored-text: var(--syntax-light-markup-ignored-text);--syntax-markup-ignored-bg: var(--syntax-light-markup-ignored-bg);--syntax-meta-diff-range: var(--syntax-light-meta-diff-range);--syntax-highlight-bg: var(--syntax-light-highlight-bg);--syntax-special-bg: var(--syntax-light-special-bg)}.highlight{color:var(--syntax-text);background-color:transparent}.highlight .hll{display:block;background-color:var(--syntax-highlight-bg)}.highlight .c{color:var(--syntax-comment)}.highlight .err{color:var(--syntax-invalid-illegal-text);background-color:var(--syntax-invalid-illegal-bg)}.highlight .g{color:var(--syntax-meta)}.highlight .k{color:var(--syntax-keyword)}.highlight .l{color:var(--syntax-meta)}.highlight .o{color:var(--syntax-constant)}.highlight .x{color:var(--syntax-meta)}.highlight .cm{color:var(--syntax-comment)}.highlight .cp{color:var(--syntax-constant)}.highlight .c1{color:var(--syntax-comment)}.highlight .cs{color:var(--syntax-comment);background-color:var(--syntax-special-bg)}.highlight .gd{color:var(--syntax-markup-deleted-text);background-color:var(--syntax-markup-deleted-bg)}.highlight .ge{color:var(--syntax-markup-italic);font-style:italic}.highlight .gr{color:var(--syntax-invalid-illegal-text);background-color:var(--syntax-invalid-illegal-bg)}.highlight .gh{color:var(--syntax-markup-heading)}.highlight .gi{color:var(--syntax-markup-inserted-text);background-color:var(--syntax-markup-inserted-bg)}.highlight .go,.highlight .gp{color:var(--syntax-meta)}.highlight .gs{color:var(--syntax-markup-bold);font-weight:700}.highlight .gu{color:var(--syntax-markup-heading)}.highlight .gt{color:var(--syntax-meta)}.highlight .kc{color:var(--syntax-constant)}.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr{color:var(--syntax-keyword)}.highlight .kt{color:var(--syntax-entity)}.highlight .ld{color:var(--syntax-meta)}.highlight .m{color:var(--syntax-constant)}.highlight .s{color:var(--syntax-string)}.highlight .il,.highlight .na{color:var(--syntax-constant)}.highlight .nb{color:var(--syntax-builtin)}.highlight .nc{color:var(--syntax-definition)}.highlight .no{color:var(--syntax-constant)}.highlight .nd,.highlight .ni{color:var(--syntax-entity)}.highlight .ne{color:var(--syntax-exception)}.highlight .nf{color:var(--syntax-definition)}.highlight .nt{color:var(--syntax-tag)}.highlight .ow{color:var(--syntax-constant)}.highlight .w{color:var(--syntax-meta)}.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:var(--syntax-constant)}.highlight .sb{color:var(--syntax-meta)}.highlight .sc{color:var(--syntax-string)}.highlight .sd{color:var(--syntax-comment)}.highlight .s2,.highlight .se{color:var(--syntax-string)}.highlight .sh{color:var(--syntax-comment)}.highlight .si,.highlight .sx{color:var(--syntax-string)}.highlight .sr{color:var(--syntax-regexp)}.highlight .s1,.highlight .ss{color:var(--syntax-string)}.highlight .bp,.highlight .vc,.highlight .vg,.highlight .vi{color:var(--syntax-variable)} diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 00000000..97d56a74 --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,566 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = docUrlRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = docUrlRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms) + ); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + `Search finished, found ${resultCount} page(s) matching the search query.` + ); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent !== undefined) return docContent.textContent; + console.warn( + "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query: (query) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + // array of [docname, title, anchor, descr, score, filename] + let results = []; + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + results.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id] of foundEntries) { + let score = Math.round(100 * queryLower.length / entry.length) + results.push([ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // lookup as object + objectTerms.forEach((term) => + results.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort((a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; + }); + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + results = results.reverse(); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord) && !terms[word]) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord) && !titleTerms[word]) + arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); + }); + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + else fileMap.set(file, [word]); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords) => { + const text = Search.htmlToText(htmlText); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/shibuya.css b/_static/shibuya.css new file mode 100644 index 00000000..611eaaee --- /dev/null +++ b/_static/shibuya.css @@ -0,0 +1 @@ +*,:after,:before{--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:rgba(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: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::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:rgba(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: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.16 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,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,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}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,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;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;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([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,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{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}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.collapse{visibility:collapse}.order-last{order:9999}.mx-auto{margin-left:auto;margin-right:auto}.ml-1{margin-left:.25rem}.mr-3{margin-right:.75rem}.block{display:block}.flex{display:flex}.table{display:table}.contents{display:contents}.hidden{display:none}.w-64{width:16rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0}.max-w-6xl{max-width:72rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.flex-grow{flex-grow:1}.-translate-x-1{--tw-translate-x:-0.25rem}.-translate-x-1,.-translate-x-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-2{--tw-translate-x:-0.5rem}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.p-6{padding:1.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pr-3{padding-right:.75rem}.pt-12{padding-top:3rem}.pt-6{padding-top:1.5rem}.pt-8{padding-top:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-sm{font-size:.875rem;line-height:1.25rem}@font-face{font-family:Twemoji Country Flags;unicode-range:u+1f1e6-1f1ff,u+1f3f4,u+e0062-e0063,u+e0065,u+e0067,u+e006c,u+e006e,u+e0073-e0074,u+e0077,u+e007f;src:url(https://cdn.jsdelivr.net/npm/country-flag-emoji-polyfill@0.1/dist/TwemojiCountryFlags.woff2) format("woff2")}::-moz-selection{color:var(--accent-a11);background-color:var(--accent-a3)}::selection{color:var(--accent-a11);background-color:var(--accent-a3)}html{scroll-behavior:smooth}body{font-family:var(--sy-f-text);color:var(--sy-c-text)}.win{font-family:"Twemoji Country Flags",var(--sy-f-text)}h1,h2,h3,h4,h5{color:var(--sy-c-heading);font-family:var(--sy-f-heading)}em,strong{color:var(--sy-c-bold)}.sy-container{max-width:90rem}.sy-scrollbar{overflow-y:auto;scrollbar-gutter:stable}.sy-scrollbar::-webkit-scrollbar{height:.75rem;width:.75rem}.sy-scrollbar::-webkit-scrollbar-thumb{border-radius:10px}.sy-scrollbar::-webkit-scrollbar-track{background-color:transparent}.sy-scrollbar:hover::-webkit-scrollbar-thumb{background-color:var(--gray-a3);background-clip:content-box;border:3px solid transparent}.i-lucide,iconify-icon{vertical-align:middle}.i-lucide{-webkit-mask:var(--icon-url) no-repeat;mask:var(--icon-url) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;width:1em;height:1em;display:inline-block;font-style:normal;background-color:currentColor}.theme-switch .theme-icon,[data-color-mode=auto] .theme-switch .theme-icon{--icon-url:var(--lucide-laptop-url)}[data-color-mode=light] .theme-switch .theme-icon{--icon-url:var(--lucide-sun-url)}[data-color-mode=dark] .theme-switch .theme-icon{--icon-url:var(--lucide-moon-url)}.light,.light-theme,:root{--tomato-1:#fffcfc;--tomato-2:#fff8f7;--tomato-3:#feebe7;--tomato-4:#ffdcd3;--tomato-5:#ffcdc2;--tomato-6:#fdbdaf;--tomato-7:#f5a898;--tomato-8:#ec8e7b;--tomato-9:#e54d2e;--tomato-10:#dd4425;--tomato-11:#d13415;--tomato-12:#5c271f}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--tomato-1:color(display-p3 0.998 0.989 0.988);--tomato-2:color(display-p3 0.994 0.974 0.969);--tomato-3:color(display-p3 0.985 0.924 0.909);--tomato-4:color(display-p3 0.996 0.868 0.835);--tomato-5:color(display-p3 0.98 0.812 0.77);--tomato-6:color(display-p3 0.953 0.75 0.698);--tomato-7:color(display-p3 0.917 0.673 0.611);--tomato-8:color(display-p3 0.875 0.575 0.502);--tomato-9:color(display-p3 0.831 0.345 0.231);--tomato-10:color(display-p3 0.802 0.313 0.2);--tomato-11:color(display-p3 0.755 0.259 0.152);--tomato-12:color(display-p3 0.335 0.165 0.132)}}}.dark,.dark-theme{--tomato-1:#181111;--tomato-2:#1f1513;--tomato-3:#391714;--tomato-4:#4e1511;--tomato-5:#5e1c16;--tomato-6:#6e2920;--tomato-7:#853a2d;--tomato-8:#ac4d39;--tomato-9:#e54d2e;--tomato-10:#ec6142;--tomato-11:#ff977d;--tomato-12:#fbd3cb}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--tomato-1:color(display-p3 0.09 0.068 0.067);--tomato-2:color(display-p3 0.115 0.084 0.076);--tomato-3:color(display-p3 0.205 0.097 0.083);--tomato-4:color(display-p3 0.282 0.099 0.077);--tomato-5:color(display-p3 0.339 0.129 0.101);--tomato-6:color(display-p3 0.398 0.179 0.141);--tomato-7:color(display-p3 0.487 0.245 0.194);--tomato-8:color(display-p3 0.629 0.322 0.248);--tomato-9:color(display-p3 0.831 0.345 0.231);--tomato-10:color(display-p3 0.862 0.415 0.298);--tomato-11:color(display-p3 1 0.585 0.455);--tomato-12:color(display-p3 0.959 0.833 0.802)}}}.light,.light-theme,:root{--tomato-a1:#ff000003;--tomato-a2:#ff200008;--tomato-a3:#f52b0018;--tomato-a4:#ff35002c;--tomato-a5:#ff2e003d;--tomato-a6:#f92d0050;--tomato-a7:#e7280067;--tomato-a8:#db250084;--tomato-a9:#df2600d1;--tomato-a10:#d72400da;--tomato-a11:#cd2200ea;--tomato-a12:#460900e0}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--tomato-a1:color(display-p3 0.675 0.024 0.024/0.012);--tomato-a2:color(display-p3 0.757 0.145 0.02/0.032);--tomato-a3:color(display-p3 0.831 0.184 0.012/0.091);--tomato-a4:color(display-p3 0.976 0.192 0.004/0.165);--tomato-a5:color(display-p3 0.918 0.192 0.004/0.232);--tomato-a6:color(display-p3 0.847 0.173 0.004/0.302);--tomato-a7:color(display-p3 0.788 0.165 0.004/0.389);--tomato-a8:color(display-p3 0.749 0.153 0.004/0.499);--tomato-a9:color(display-p3 0.78 0.149 0/0.769);--tomato-a10:color(display-p3 0.757 0.141 0/0.8);--tomato-a11:color(display-p3 0.755 0.259 0.152);--tomato-a12:color(display-p3 0.335 0.165 0.132)}}}.dark,.dark-theme{--tomato-a1:#f1121208;--tomato-a2:#ff55330f;--tomato-a3:#ff35232b;--tomato-a4:#fd201142;--tomato-a5:#fe332153;--tomato-a6:#ff4f3864;--tomato-a7:#fd644a7d;--tomato-a8:#fe6d4ea7;--tomato-a9:#fe5431e4;--tomato-a10:#ff6847eb;--tomato-a11:#ff977d;--tomato-a12:#ffd6cefb}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--tomato-a1:color(display-p3 0.973 0.071 0.071/0.026);--tomato-a2:color(display-p3 0.992 0.376 0.224/0.051);--tomato-a3:color(display-p3 0.996 0.282 0.176/0.148);--tomato-a4:color(display-p3 1 0.204 0.118/0.232);--tomato-a5:color(display-p3 1 0.286 0.192/0.29);--tomato-a6:color(display-p3 1 0.392 0.278/0.353);--tomato-a7:color(display-p3 1 0.459 0.349/0.45);--tomato-a8:color(display-p3 1 0.49 0.369/0.601);--tomato-a9:color(display-p3 1 0.408 0.267/0.82);--tomato-a10:color(display-p3 1 0.478 0.341/0.853);--tomato-a11:color(display-p3 1 0.585 0.455);--tomato-a12:color(display-p3 0.959 0.833 0.802)}}}.light,.light-theme,:root{--red-1:#fffcfc;--red-2:#fff7f7;--red-3:#feebec;--red-4:#ffdbdc;--red-5:#ffcdce;--red-6:#fdbdbe;--red-7:#f4a9aa;--red-8:#eb8e90;--red-9:#e5484d;--red-10:#dc3e42;--red-11:#ce2c31;--red-12:#641723}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--red-1:color(display-p3 0.998 0.989 0.988);--red-2:color(display-p3 0.995 0.971 0.971);--red-3:color(display-p3 0.985 0.925 0.925);--red-4:color(display-p3 0.999 0.866 0.866);--red-5:color(display-p3 0.984 0.812 0.811);--red-6:color(display-p3 0.955 0.751 0.749);--red-7:color(display-p3 0.915 0.675 0.672);--red-8:color(display-p3 0.872 0.575 0.572);--red-9:color(display-p3 0.83 0.329 0.324);--red-10:color(display-p3 0.798 0.294 0.285);--red-11:color(display-p3 0.744 0.234 0.222);--red-12:color(display-p3 0.36 0.115 0.143)}}}.dark,.dark-theme{--red-1:#191111;--red-2:#201314;--red-3:#3b1219;--red-4:#500f1c;--red-5:#611623;--red-6:#72232d;--red-7:#8c333a;--red-8:#b54548;--red-9:#e5484d;--red-10:#ec5d5e;--red-11:#ff9592;--red-12:#ffd1d9}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--red-1:color(display-p3 0.093 0.068 0.067);--red-2:color(display-p3 0.118 0.077 0.079);--red-3:color(display-p3 0.211 0.081 0.099);--red-4:color(display-p3 0.287 0.079 0.113);--red-5:color(display-p3 0.348 0.11 0.142);--red-6:color(display-p3 0.414 0.16 0.183);--red-7:color(display-p3 0.508 0.224 0.236);--red-8:color(display-p3 0.659 0.298 0.297);--red-9:color(display-p3 0.83 0.329 0.324);--red-10:color(display-p3 0.861 0.403 0.387);--red-11:color(display-p3 1 0.57 0.55);--red-12:color(display-p3 0.971 0.826 0.852)}}}.light,.light-theme,:root{--red-a1:#ff000003;--red-a2:#ff000008;--red-a3:#f3000d14;--red-a4:#ff000824;--red-a5:#ff000632;--red-a6:#f8000442;--red-a7:#df000356;--red-a8:#d2000571;--red-a9:#db0007b7;--red-a10:#d10005c1;--red-a11:#c40006d3;--red-a12:#55000de8}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--red-a1:color(display-p3 0.675 0.024 0.024/0.012);--red-a2:color(display-p3 0.863 0.024 0.024/0.028);--red-a3:color(display-p3 0.792 0.008 0.008/0.075);--red-a4:color(display-p3 1 0.008 0.008/0.134);--red-a5:color(display-p3 0.918 0.008 0.008/0.189);--red-a6:color(display-p3 0.831 0.02 0.004/0.251);--red-a7:color(display-p3 0.741 0.016 0.004/0.33);--red-a8:color(display-p3 0.698 0.012 0.004/0.428);--red-a9:color(display-p3 0.749 0.008 0/0.675);--red-a10:color(display-p3 0.714 0.012 0/0.714);--red-a11:color(display-p3 0.744 0.234 0.222);--red-a12:color(display-p3 0.36 0.115 0.143)}}}.dark,.dark-theme{--red-a1:#f4121209;--red-a2:#f22f3e11;--red-a3:#ff173f2d;--red-a4:#fe0a3b44;--red-a5:#ff204756;--red-a6:#ff3e5668;--red-a7:#ff536184;--red-a8:#ff5d61b0;--red-a9:#fe4e54e4;--red-a10:#ff6465eb;--red-a11:#ff9592;--red-a12:#ffd1d9}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--red-a1:color(display-p3 0.984 0.071 0.071/0.03);--red-a2:color(display-p3 0.996 0.282 0.282/0.055);--red-a3:color(display-p3 1 0.169 0.271/0.156);--red-a4:color(display-p3 1 0.118 0.267/0.236);--red-a5:color(display-p3 1 0.212 0.314/0.303);--red-a6:color(display-p3 1 0.318 0.38/0.374);--red-a7:color(display-p3 1 0.4 0.424/0.475);--red-a8:color(display-p3 1 0.431 0.431/0.635);--red-a9:color(display-p3 1 0.388 0.384/0.82);--red-a10:color(display-p3 1 0.463 0.447/0.853);--red-a11:color(display-p3 1 0.57 0.55);--red-a12:color(display-p3 0.971 0.826 0.852)}}}.light,.light-theme,:root{--ruby-1:#fffcfd;--ruby-2:#fff7f8;--ruby-3:#feeaed;--ruby-4:#ffdce1;--ruby-5:#ffced6;--ruby-6:#f8bfc8;--ruby-7:#efacb8;--ruby-8:#e592a3;--ruby-9:#e54666;--ruby-10:#dc3b5d;--ruby-11:#ca244d;--ruby-12:#64172b}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--ruby-1:color(display-p3 0.998 0.989 0.992);--ruby-2:color(display-p3 0.995 0.971 0.974);--ruby-3:color(display-p3 0.983 0.92 0.928);--ruby-4:color(display-p3 0.987 0.869 0.885);--ruby-5:color(display-p3 0.968 0.817 0.839);--ruby-6:color(display-p3 0.937 0.758 0.786);--ruby-7:color(display-p3 0.897 0.685 0.721);--ruby-8:color(display-p3 0.851 0.588 0.639);--ruby-9:color(display-p3 0.83 0.323 0.408);--ruby-10:color(display-p3 0.795 0.286 0.375);--ruby-11:color(display-p3 0.728 0.211 0.311);--ruby-12:color(display-p3 0.36 0.115 0.171)}}}.dark,.dark-theme{--ruby-1:#191113;--ruby-2:#1e1517;--ruby-3:#3a141e;--ruby-4:#4e1325;--ruby-5:#5e1a2e;--ruby-6:#6f2539;--ruby-7:#883447;--ruby-8:#b3445a;--ruby-9:#e54666;--ruby-10:#ec5a72;--ruby-11:#ff949d;--ruby-12:#fed2e1}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--ruby-1:color(display-p3 0.093 0.068 0.074);--ruby-2:color(display-p3 0.113 0.083 0.089);--ruby-3:color(display-p3 0.208 0.088 0.117);--ruby-4:color(display-p3 0.279 0.092 0.147);--ruby-5:color(display-p3 0.337 0.12 0.18);--ruby-6:color(display-p3 0.401 0.166 0.223);--ruby-7:color(display-p3 0.495 0.224 0.281);--ruby-8:color(display-p3 0.652 0.295 0.359);--ruby-9:color(display-p3 0.83 0.323 0.408);--ruby-10:color(display-p3 0.857 0.392 0.455);--ruby-11:color(display-p3 1 0.57 0.59);--ruby-12:color(display-p3 0.968 0.83 0.88)}}}.light,.light-theme,:root{--ruby-a1:#ff005503;--ruby-a2:#ff002008;--ruby-a3:#f3002515;--ruby-a4:#ff002523;--ruby-a5:#ff002a31;--ruby-a6:#e4002440;--ruby-a7:#ce002553;--ruby-a8:#c300286d;--ruby-a9:#db002cb9;--ruby-a10:#d2002cc4;--ruby-a11:#c10030db;--ruby-a12:#550016e8}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--ruby-a1:color(display-p3 0.675 0.024 0.349/0.012);--ruby-a2:color(display-p3 0.863 0.024 0.024/0.028);--ruby-a3:color(display-p3 0.804 0.008 0.11/0.079);--ruby-a4:color(display-p3 0.91 0.008 0.125/0.13);--ruby-a5:color(display-p3 0.831 0.004 0.133/0.185);--ruby-a6:color(display-p3 0.745 0.004 0.118/0.244);--ruby-a7:color(display-p3 0.678 0.004 0.114/0.314);--ruby-a8:color(display-p3 0.639 0.004 0.125/0.412);--ruby-a9:color(display-p3 0.753 0 0.129/0.679);--ruby-a10:color(display-p3 0.714 0 0.125/0.714);--ruby-a11:color(display-p3 0.728 0.211 0.311);--ruby-a12:color(display-p3 0.36 0.115 0.171)}}}.dark,.dark-theme{--ruby-a1:#f4124a09;--ruby-a2:#fe5a7f0e;--ruby-a3:#ff235d2c;--ruby-a4:#fd195e42;--ruby-a5:#fe2d6b53;--ruby-a6:#ff447665;--ruby-a7:#ff577d80;--ruby-a8:#ff5c7cae;--ruby-a9:#fe4c70e4;--ruby-a10:#ff617beb;--ruby-a11:#ff949d;--ruby-a12:#ffd3e2}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--ruby-a1:color(display-p3 0.984 0.071 0.329/0.03);--ruby-a2:color(display-p3 0.992 0.376 0.529/0.051);--ruby-a3:color(display-p3 0.996 0.196 0.404/0.152);--ruby-a4:color(display-p3 1 0.173 0.416/0.227);--ruby-a5:color(display-p3 1 0.259 0.459/0.29);--ruby-a6:color(display-p3 1 0.341 0.506/0.358);--ruby-a7:color(display-p3 1 0.412 0.541/0.458);--ruby-a8:color(display-p3 1 0.431 0.537/0.627);--ruby-a9:color(display-p3 1 0.376 0.482/0.82);--ruby-a10:color(display-p3 1 0.447 0.522/0.849);--ruby-a11:color(display-p3 1 0.57 0.59);--ruby-a12:color(display-p3 0.968 0.83 0.88)}}}.light,.light-theme,:root{--crimson-1:#fffcfd;--crimson-2:#fef7f9;--crimson-3:#ffe9f0;--crimson-4:#fedce7;--crimson-5:#facedd;--crimson-6:#f3bed1;--crimson-7:#eaacc3;--crimson-8:#e093b2;--crimson-9:#e93d82;--crimson-10:#df3478;--crimson-11:#cb1d63;--crimson-12:#621639}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--crimson-1:color(display-p3 0.998 0.989 0.992);--crimson-2:color(display-p3 0.991 0.969 0.976);--crimson-3:color(display-p3 0.987 0.917 0.941);--crimson-4:color(display-p3 0.975 0.866 0.904);--crimson-5:color(display-p3 0.953 0.813 0.864);--crimson-6:color(display-p3 0.921 0.755 0.817);--crimson-7:color(display-p3 0.88 0.683 0.761);--crimson-8:color(display-p3 0.834 0.592 0.694);--crimson-9:color(display-p3 0.843 0.298 0.507);--crimson-10:color(display-p3 0.807 0.266 0.468);--crimson-11:color(display-p3 0.731 0.195 0.388);--crimson-12:color(display-p3 0.352 0.111 0.221)}}}.dark,.dark-theme{--crimson-1:#191114;--crimson-2:#201318;--crimson-3:#381525;--crimson-4:#4d122f;--crimson-5:#5c1839;--crimson-6:#6d2545;--crimson-7:#873356;--crimson-8:#b0436e;--crimson-9:#e93d82;--crimson-10:#ee518a;--crimson-11:#ff92ad;--crimson-12:#fdd3e8}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--crimson-1:color(display-p3 0.093 0.068 0.078);--crimson-2:color(display-p3 0.117 0.078 0.095);--crimson-3:color(display-p3 0.203 0.091 0.143);--crimson-4:color(display-p3 0.277 0.087 0.182);--crimson-5:color(display-p3 0.332 0.115 0.22);--crimson-6:color(display-p3 0.394 0.162 0.268);--crimson-7:color(display-p3 0.489 0.222 0.336);--crimson-8:color(display-p3 0.638 0.289 0.429);--crimson-9:color(display-p3 0.843 0.298 0.507);--crimson-10:color(display-p3 0.864 0.364 0.539);--crimson-11:color(display-p3 1 0.56 0.66);--crimson-12:color(display-p3 0.966 0.834 0.906)}}}.light,.light-theme,:root{--crimson-a1:#ff005503;--crimson-a2:#e0004008;--crimson-a3:#ff005216;--crimson-a4:#f8005123;--crimson-a5:#e5004f31;--crimson-a6:#d0004b41;--crimson-a7:#bf004753;--crimson-a8:#b6004a6c;--crimson-a9:#e2005bc2;--crimson-a10:#d70056cb;--crimson-a11:#c4004fe2;--crimson-a12:#530026e9}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--crimson-a1:color(display-p3 0.675 0.024 0.349/0.012);--crimson-a2:color(display-p3 0.757 0.02 0.267/0.032);--crimson-a3:color(display-p3 0.859 0.008 0.294/0.083);--crimson-a4:color(display-p3 0.827 0.008 0.298/0.134);--crimson-a5:color(display-p3 0.753 0.008 0.275/0.189);--crimson-a6:color(display-p3 0.682 0.004 0.247/0.244);--crimson-a7:color(display-p3 0.62 0.004 0.251/0.318);--crimson-a8:color(display-p3 0.6 0.004 0.251/0.408);--crimson-a9:color(display-p3 0.776 0 0.298/0.702);--crimson-a10:color(display-p3 0.737 0 0.275/0.734);--crimson-a11:color(display-p3 0.731 0.195 0.388);--crimson-a12:color(display-p3 0.352 0.111 0.221)}}}.dark,.dark-theme{--crimson-a1:#f4126709;--crimson-a2:#f22f7a11;--crimson-a3:#fe2a8b2a;--crimson-a4:#fd158741;--crimson-a5:#fd278f51;--crimson-a6:#fe459763;--crimson-a7:#fd559b7f;--crimson-a8:#fe5b9bab;--crimson-a9:#fe418de8;--crimson-a10:#ff5693ed;--crimson-a11:#ff92ad;--crimson-a12:#ffd5eafd}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--crimson-a1:color(display-p3 0.984 0.071 0.463/0.03);--crimson-a2:color(display-p3 0.996 0.282 0.569/0.055);--crimson-a3:color(display-p3 0.996 0.227 0.573/0.148);--crimson-a4:color(display-p3 1 0.157 0.569/0.227);--crimson-a5:color(display-p3 1 0.231 0.604/0.286);--crimson-a6:color(display-p3 1 0.337 0.643/0.349);--crimson-a7:color(display-p3 1 0.416 0.663/0.454);--crimson-a8:color(display-p3 0.996 0.427 0.651/0.614);--crimson-a9:color(display-p3 1 0.345 0.596/0.832);--crimson-a10:color(display-p3 1 0.42 0.62/0.853);--crimson-a11:color(display-p3 1 0.56 0.66);--crimson-a12:color(display-p3 0.966 0.834 0.906)}}}.light,.light-theme,:root{--pink-1:#fffcfe;--pink-2:#fef7fb;--pink-3:#fee9f5;--pink-4:#fbdcef;--pink-5:#f6cee7;--pink-6:#efbfdd;--pink-7:#e7acd0;--pink-8:#dd93c2;--pink-9:#d6409f;--pink-10:#cf3897;--pink-11:#c2298a;--pink-12:#651249}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--pink-1:color(display-p3 0.998 0.989 0.996);--pink-2:color(display-p3 0.992 0.97 0.985);--pink-3:color(display-p3 0.981 0.917 0.96);--pink-4:color(display-p3 0.963 0.867 0.932);--pink-5:color(display-p3 0.939 0.815 0.899);--pink-6:color(display-p3 0.907 0.756 0.859);--pink-7:color(display-p3 0.869 0.683 0.81);--pink-8:color(display-p3 0.825 0.59 0.751);--pink-9:color(display-p3 0.775 0.297 0.61);--pink-10:color(display-p3 0.748 0.27 0.581);--pink-11:color(display-p3 0.698 0.219 0.528);--pink-12:color(display-p3 0.363 0.101 0.279)}}}.dark,.dark-theme{--pink-1:#191117;--pink-2:#21121d;--pink-3:#37172f;--pink-4:#4b143d;--pink-5:#591c47;--pink-6:#692955;--pink-7:#833869;--pink-8:#a84885;--pink-9:#d6409f;--pink-10:#de51a8;--pink-11:#ff8dcc;--pink-12:#fdd1ea}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--pink-1:color(display-p3 0.093 0.068 0.089);--pink-2:color(display-p3 0.121 0.073 0.11);--pink-3:color(display-p3 0.198 0.098 0.179);--pink-4:color(display-p3 0.271 0.095 0.231);--pink-5:color(display-p3 0.32 0.127 0.273);--pink-6:color(display-p3 0.382 0.177 0.326);--pink-7:color(display-p3 0.477 0.238 0.405);--pink-8:color(display-p3 0.612 0.304 0.51);--pink-9:color(display-p3 0.775 0.297 0.61);--pink-10:color(display-p3 0.808 0.356 0.645);--pink-11:color(display-p3 1 0.535 0.78);--pink-12:color(display-p3 0.964 0.826 0.912)}}}.light,.light-theme,:root{--pink-a1:#ff00aa03;--pink-a2:#e0008008;--pink-a3:#f4008c16;--pink-a4:#e2008b23;--pink-a5:#d1008331;--pink-a6:#c0007840;--pink-a7:#b6006f53;--pink-a8:#af006f6c;--pink-a9:#c8007fbf;--pink-a10:#c2007ac7;--pink-a11:#b60074d6;--pink-a12:#59003bed}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--pink-a1:color(display-p3 0.675 0.024 0.675/0.012);--pink-a2:color(display-p3 0.757 0.02 0.51/0.032);--pink-a3:color(display-p3 0.765 0.008 0.529/0.083);--pink-a4:color(display-p3 0.737 0.008 0.506/0.134);--pink-a5:color(display-p3 0.663 0.004 0.451/0.185);--pink-a6:color(display-p3 0.616 0.004 0.424/0.244);--pink-a7:color(display-p3 0.596 0.004 0.412/0.318);--pink-a8:color(display-p3 0.573 0.004 0.404/0.412);--pink-a9:color(display-p3 0.682 0 0.447/0.702);--pink-a10:color(display-p3 0.655 0 0.424/0.73);--pink-a11:color(display-p3 0.698 0.219 0.528);--pink-a12:color(display-p3 0.363 0.101 0.279)}}}.dark,.dark-theme{--pink-a1:#f412bc09;--pink-a2:#f420bb12;--pink-a3:#fe37cc29;--pink-a4:#fc1ec43f;--pink-a5:#fd35c24e;--pink-a6:#fd51c75f;--pink-a7:#fd62c87b;--pink-a8:#ff68c8a2;--pink-a9:#fe49bcd4;--pink-a10:#ff5cc0dc;--pink-a11:#ff8dcc;--pink-a12:#ffd3ecfd}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--pink-a1:color(display-p3 0.984 0.071 0.855/0.03);--pink-a2:color(display-p3 1 0.2 0.8/0.059);--pink-a3:color(display-p3 1 0.294 0.886/0.139);--pink-a4:color(display-p3 1 0.192 0.82/0.219);--pink-a5:color(display-p3 1 0.282 0.827/0.274);--pink-a6:color(display-p3 1 0.396 0.835/0.337);--pink-a7:color(display-p3 1 0.459 0.831/0.442);--pink-a8:color(display-p3 1 0.478 0.827/0.585);--pink-a9:color(display-p3 1 0.373 0.784/0.761);--pink-a10:color(display-p3 1 0.435 0.792/0.795);--pink-a11:color(display-p3 1 0.535 0.78);--pink-a12:color(display-p3 0.964 0.826 0.912)}}}.light,.light-theme,:root{--plum-1:#fefcff;--plum-2:#fdf7fd;--plum-3:#fbebfb;--plum-4:#f7def8;--plum-5:#f2d1f3;--plum-6:#e9c2ec;--plum-7:#deade3;--plum-8:#cf91d8;--plum-9:#ab4aba;--plum-10:#a144af;--plum-11:#953ea3;--plum-12:#53195d}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--plum-1:color(display-p3 0.995 0.988 0.999);--plum-2:color(display-p3 0.988 0.971 0.99);--plum-3:color(display-p3 0.973 0.923 0.98);--plum-4:color(display-p3 0.953 0.875 0.966);--plum-5:color(display-p3 0.926 0.825 0.945);--plum-6:color(display-p3 0.89 0.765 0.916);--plum-7:color(display-p3 0.84 0.686 0.877);--plum-8:color(display-p3 0.775 0.58 0.832);--plum-9:color(display-p3 0.624 0.313 0.708);--plum-10:color(display-p3 0.587 0.29 0.667);--plum-11:color(display-p3 0.543 0.263 0.619);--plum-12:color(display-p3 0.299 0.114 0.352)}}}.dark,.dark-theme{--plum-1:#181118;--plum-2:#201320;--plum-3:#351a35;--plum-4:#451d47;--plum-5:#512454;--plum-6:#5e3061;--plum-7:#734079;--plum-8:#92549c;--plum-9:#ab4aba;--plum-10:#b658c4;--plum-11:#e796f3;--plum-12:#f4d4f4}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--plum-1:color(display-p3 0.09 0.068 0.092);--plum-2:color(display-p3 0.118 0.077 0.121);--plum-3:color(display-p3 0.192 0.105 0.202);--plum-4:color(display-p3 0.25 0.121 0.271);--plum-5:color(display-p3 0.293 0.152 0.319);--plum-6:color(display-p3 0.343 0.198 0.372);--plum-7:color(display-p3 0.424 0.262 0.461);--plum-8:color(display-p3 0.54 0.341 0.595);--plum-9:color(display-p3 0.624 0.313 0.708);--plum-10:color(display-p3 0.666 0.365 0.748);--plum-11:color(display-p3 0.86 0.602 0.933);--plum-12:color(display-p3 0.936 0.836 0.949)}}}.light,.light-theme,:root{--plum-a1:#aa00ff03;--plum-a2:#c000c008;--plum-a3:#cc00cc14;--plum-a4:#c200c921;--plum-a5:#b700bd2e;--plum-a6:#a400b03d;--plum-a7:#9900a852;--plum-a8:#9000a56e;--plum-a9:#89009eb5;--plum-a10:#7f0092bb;--plum-a11:#730086c1;--plum-a12:#40004be6}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--plum-a1:color(display-p3 0.675 0.024 1/0.012);--plum-a2:color(display-p3 0.58 0.024 0.58/0.028);--plum-a3:color(display-p3 0.655 0.008 0.753/0.079);--plum-a4:color(display-p3 0.627 0.008 0.722/0.126);--plum-a5:color(display-p3 0.58 0.004 0.69/0.177);--plum-a6:color(display-p3 0.537 0.004 0.655/0.236);--plum-a7:color(display-p3 0.49 0.004 0.616/0.314);--plum-a8:color(display-p3 0.471 0.004 0.6/0.42);--plum-a9:color(display-p3 0.451 0 0.576/0.687);--plum-a10:color(display-p3 0.42 0 0.529/0.71);--plum-a11:color(display-p3 0.543 0.263 0.619);--plum-a12:color(display-p3 0.299 0.114 0.352)}}}.dark,.dark-theme{--plum-a1:#f112f108;--plum-a2:#f22ff211;--plum-a3:#fd4cfd27;--plum-a4:#f646ff3a;--plum-a5:#f455ff48;--plum-a6:#f66dff56;--plum-a7:#f07cfd70;--plum-a8:#ee84ff95;--plum-a9:#e961feb6;--plum-a10:#ed70ffc0;--plum-a11:#f19cfef3;--plum-a12:#feddfef4}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--plum-a1:color(display-p3 0.973 0.071 0.973/0.026);--plum-a2:color(display-p3 0.933 0.267 1/0.059);--plum-a3:color(display-p3 0.918 0.333 0.996/0.148);--plum-a4:color(display-p3 0.91 0.318 1/0.219);--plum-a5:color(display-p3 0.914 0.388 1/0.269);--plum-a6:color(display-p3 0.906 0.463 1/0.328);--plum-a7:color(display-p3 0.906 0.529 1/0.425);--plum-a8:color(display-p3 0.906 0.553 1/0.568);--plum-a9:color(display-p3 0.875 0.427 1/0.69);--plum-a10:color(display-p3 0.886 0.471 0.996/0.732);--plum-a11:color(display-p3 0.86 0.602 0.933);--plum-a12:color(display-p3 0.936 0.836 0.949)}}}.light,.light-theme,:root{--purple-1:#fefcfe;--purple-2:#fbf7fe;--purple-3:#f7edfe;--purple-4:#f2e2fc;--purple-5:#ead5f9;--purple-6:#e0c4f4;--purple-7:#d1afec;--purple-8:#be93e4;--purple-9:#8e4ec6;--purple-10:#8347b9;--purple-11:#8145b5;--purple-12:#402060}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--purple-1:color(display-p3 0.995 0.988 0.996);--purple-2:color(display-p3 0.983 0.971 0.993);--purple-3:color(display-p3 0.963 0.931 0.989);--purple-4:color(display-p3 0.937 0.888 0.981);--purple-5:color(display-p3 0.904 0.837 0.966);--purple-6:color(display-p3 0.86 0.774 0.942);--purple-7:color(display-p3 0.799 0.69 0.91);--purple-8:color(display-p3 0.719 0.583 0.874);--purple-9:color(display-p3 0.523 0.318 0.751);--purple-10:color(display-p3 0.483 0.289 0.7);--purple-11:color(display-p3 0.473 0.281 0.687);--purple-12:color(display-p3 0.234 0.132 0.363)}}}.dark,.dark-theme{--purple-1:#18111b;--purple-2:#1e1523;--purple-3:#301c3b;--purple-4:#3d224e;--purple-5:#48295c;--purple-6:#54346b;--purple-7:#664282;--purple-8:#8457aa;--purple-9:#8e4ec6;--purple-10:#9a5cd0;--purple-11:#d19dff;--purple-12:#ecd9fa}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--purple-1:color(display-p3 0.09 0.068 0.103);--purple-2:color(display-p3 0.113 0.082 0.134);--purple-3:color(display-p3 0.175 0.112 0.224);--purple-4:color(display-p3 0.224 0.137 0.297);--purple-5:color(display-p3 0.264 0.167 0.349);--purple-6:color(display-p3 0.311 0.208 0.406);--purple-7:color(display-p3 0.381 0.266 0.496);--purple-8:color(display-p3 0.49 0.349 0.649);--purple-9:color(display-p3 0.523 0.318 0.751);--purple-10:color(display-p3 0.57 0.373 0.791);--purple-11:color(display-p3 0.8 0.62 1);--purple-12:color(display-p3 0.913 0.854 0.971)}}}.light,.light-theme,:root{--purple-a1:#aa00aa03;--purple-a2:#8000e008;--purple-a3:#8e00f112;--purple-a4:#8d00e51d;--purple-a5:#8000db2a;--purple-a6:#7a01d03b;--purple-a7:#6d00c350;--purple-a8:#6600c06c;--purple-a9:#5c00adb1;--purple-a10:#53009eb8;--purple-a11:#52009aba;--purple-a12:#250049df}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--purple-a1:color(display-p3 0.675 0.024 0.675/0.012);--purple-a2:color(display-p3 0.443 0.024 0.722/0.028);--purple-a3:color(display-p3 0.506 0.008 0.835/0.071);--purple-a4:color(display-p3 0.451 0.004 0.831/0.114);--purple-a5:color(display-p3 0.431 0.004 0.788/0.165);--purple-a6:color(display-p3 0.384 0.004 0.745/0.228);--purple-a7:color(display-p3 0.357 0.004 0.71/0.31);--purple-a8:color(display-p3 0.322 0.004 0.702/0.416);--purple-a9:color(display-p3 0.298 0 0.639/0.683);--purple-a10:color(display-p3 0.271 0 0.58/0.71);--purple-a11:color(display-p3 0.473 0.281 0.687);--purple-a12:color(display-p3 0.234 0.132 0.363)}}}.dark,.dark-theme{--purple-a1:#b412f90b;--purple-a2:#b744f714;--purple-a3:#c150ff2d;--purple-a4:#bb53fd42;--purple-a5:#be5cfd51;--purple-a6:#c16dfd61;--purple-a7:#c378fd7a;--purple-a8:#c47effa4;--purple-a9:#b661ffc2;--purple-a10:#bc6fffcd;--purple-a11:#d19dff;--purple-a12:#f1ddfffa}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--purple-a1:color(display-p3 0.686 0.071 0.996/0.038);--purple-a2:color(display-p3 0.722 0.286 0.996/0.072);--purple-a3:color(display-p3 0.718 0.349 0.996/0.169);--purple-a4:color(display-p3 0.702 0.353 1/0.248);--purple-a5:color(display-p3 0.718 0.404 1/0.303);--purple-a6:color(display-p3 0.733 0.455 1/0.366);--purple-a7:color(display-p3 0.753 0.506 1/0.458);--purple-a8:color(display-p3 0.749 0.522 1/0.622);--purple-a9:color(display-p3 0.686 0.408 1/0.736);--purple-a10:color(display-p3 0.71 0.459 1/0.778);--purple-a11:color(display-p3 0.8 0.62 1);--purple-a12:color(display-p3 0.913 0.854 0.971)}}}.light,.light-theme,:root{--violet-1:#fdfcfe;--violet-2:#faf8ff;--violet-3:#f4f0fe;--violet-4:#ebe4ff;--violet-5:#e1d9ff;--violet-6:#d4cafe;--violet-7:#c2b5f5;--violet-8:#aa99ec;--violet-9:#6e56cf;--violet-10:#654dc4;--violet-11:#6550b9;--violet-12:#2f265f}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--violet-1:color(display-p3 0.991 0.988 0.995);--violet-2:color(display-p3 0.978 0.974 0.998);--violet-3:color(display-p3 0.953 0.943 0.993);--violet-4:color(display-p3 0.916 0.897 1);--violet-5:color(display-p3 0.876 0.851 1);--violet-6:color(display-p3 0.825 0.793 0.981);--violet-7:color(display-p3 0.752 0.712 0.943);--violet-8:color(display-p3 0.654 0.602 0.902);--violet-9:color(display-p3 0.417 0.341 0.784);--violet-10:color(display-p3 0.381 0.306 0.741);--violet-11:color(display-p3 0.383 0.317 0.702);--violet-12:color(display-p3 0.179 0.15 0.359)}}}.dark,.dark-theme{--violet-1:#14121f;--violet-2:#1b1525;--violet-3:#291f43;--violet-4:#33255b;--violet-5:#3c2e69;--violet-6:#473876;--violet-7:#56468b;--violet-8:#6958ad;--violet-9:#6e56cf;--violet-10:#7d66d9;--violet-11:#baa7ff;--violet-12:#e2ddfe}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--violet-1:color(display-p3 0.077 0.071 0.118);--violet-2:color(display-p3 0.101 0.084 0.141);--violet-3:color(display-p3 0.154 0.123 0.256);--violet-4:color(display-p3 0.191 0.148 0.345);--violet-5:color(display-p3 0.226 0.182 0.396);--violet-6:color(display-p3 0.269 0.223 0.449);--violet-7:color(display-p3 0.326 0.277 0.53);--violet-8:color(display-p3 0.399 0.346 0.656);--violet-9:color(display-p3 0.417 0.341 0.784);--violet-10:color(display-p3 0.477 0.402 0.823);--violet-11:color(display-p3 0.72 0.65 1);--violet-12:color(display-p3 0.883 0.867 0.986)}}}.light,.light-theme,:root{--violet-a1:#5500aa03;--violet-a2:#4900ff07;--violet-a3:#4400ee0f;--violet-a4:#4300ff1b;--violet-a5:#3600ff26;--violet-a6:#3100fb35;--violet-a7:#2d01dd4a;--violet-a8:#2b00d066;--violet-a9:#2400b7a9;--violet-a10:#2300abb2;--violet-a11:#1f0099af;--violet-a12:#0b0043d9}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--violet-a1:color(display-p3 0.349 0.024 0.675/0.012);--violet-a2:color(display-p3 0.161 0.024 0.863/0.028);--violet-a3:color(display-p3 0.204 0.004 0.871/0.059);--violet-a4:color(display-p3 0.196 0.004 1/0.102);--violet-a5:color(display-p3 0.165 0.008 1/0.15);--violet-a6:color(display-p3 0.153 0.004 0.906/0.208);--violet-a7:color(display-p3 0.141 0.004 0.796/0.287);--violet-a8:color(display-p3 0.133 0.004 0.753/0.397);--violet-a9:color(display-p3 0.114 0 0.675/0.659);--violet-a10:color(display-p3 0.11 0 0.627/0.695);--violet-a11:color(display-p3 0.383 0.317 0.702);--violet-a12:color(display-p3 0.179 0.15 0.359)}}}.dark,.dark-theme{--violet-a1:#4422ff0f;--violet-a2:#853ff916;--violet-a3:#8354fe36;--violet-a4:#7d51fd50;--violet-a5:#845ffd5f;--violet-a6:#8f6cfd6d;--violet-a7:#9879ff83;--violet-a8:#977dfea8;--violet-a9:#8668ffcc;--violet-a10:#9176fed7;--violet-a11:#baa7ff;--violet-a12:#e3deff}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--violet-a1:color(display-p3 0.282 0.141 0.996/0.055);--violet-a2:color(display-p3 0.51 0.263 1/0.08);--violet-a3:color(display-p3 0.494 0.337 0.996/0.202);--violet-a4:color(display-p3 0.49 0.345 1/0.299);--violet-a5:color(display-p3 0.525 0.392 1/0.353);--violet-a6:color(display-p3 0.569 0.455 1/0.408);--violet-a7:color(display-p3 0.588 0.494 1/0.496);--violet-a8:color(display-p3 0.596 0.51 1/0.631);--violet-a9:color(display-p3 0.522 0.424 1/0.769);--violet-a10:color(display-p3 0.576 0.482 1/0.811);--violet-a11:color(display-p3 0.72 0.65 1);--violet-a12:color(display-p3 0.883 0.867 0.986)}}}.light,.light-theme,:root{--iris-1:#fdfdff;--iris-2:#f8f8ff;--iris-3:#f0f1fe;--iris-4:#e6e7ff;--iris-5:#dadcff;--iris-6:#cbcdff;--iris-7:#b8baf8;--iris-8:#9b9ef0;--iris-9:#5b5bd6;--iris-10:#5151cd;--iris-11:#5753c6;--iris-12:#272962}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--iris-1:color(display-p3 0.992 0.992 0.999);--iris-2:color(display-p3 0.972 0.973 0.998);--iris-3:color(display-p3 0.943 0.945 0.992);--iris-4:color(display-p3 0.902 0.906 1);--iris-5:color(display-p3 0.857 0.861 1);--iris-6:color(display-p3 0.799 0.805 0.987);--iris-7:color(display-p3 0.721 0.727 0.955);--iris-8:color(display-p3 0.61 0.619 0.918);--iris-9:color(display-p3 0.357 0.357 0.81);--iris-10:color(display-p3 0.318 0.318 0.774);--iris-11:color(display-p3 0.337 0.326 0.748);--iris-12:color(display-p3 0.154 0.161 0.371)}}}.dark,.dark-theme{--iris-1:#13131e;--iris-2:#171625;--iris-3:#202248;--iris-4:#262a65;--iris-5:#303374;--iris-6:#3d3e82;--iris-7:#4a4a95;--iris-8:#5958b1;--iris-9:#5b5bd6;--iris-10:#6e6ade;--iris-11:#b1a9ff;--iris-12:#e0dffe}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--iris-1:color(display-p3 0.075 0.075 0.114);--iris-2:color(display-p3 0.089 0.086 0.14);--iris-3:color(display-p3 0.128 0.134 0.272);--iris-4:color(display-p3 0.153 0.165 0.382);--iris-5:color(display-p3 0.192 0.201 0.44);--iris-6:color(display-p3 0.239 0.241 0.491);--iris-7:color(display-p3 0.291 0.289 0.565);--iris-8:color(display-p3 0.35 0.345 0.673);--iris-9:color(display-p3 0.357 0.357 0.81);--iris-10:color(display-p3 0.428 0.416 0.843);--iris-11:color(display-p3 0.685 0.662 1);--iris-12:color(display-p3 0.878 0.875 0.986)}}}.light,.light-theme,:root{--iris-a1:#0000ff02;--iris-a2:#0000ff07;--iris-a3:#0011ee0f;--iris-a4:#000bff19;--iris-a5:#000eff25;--iris-a6:#000aff34;--iris-a7:#0008e647;--iris-a8:#0008d964;--iris-a9:#0000c0a4;--iris-a10:#0000b6ae;--iris-a11:#0600abac;--iris-a12:#000246d8}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--iris-a1:color(display-p3 0.02 0.02 1/0.008);--iris-a2:color(display-p3 0.024 0.024 0.863/0.028);--iris-a3:color(display-p3 0.004 0.071 0.871/0.059);--iris-a4:color(display-p3 0.012 0.051 1/0.099);--iris-a5:color(display-p3 0.008 0.035 1/0.142);--iris-a6:color(display-p3 0 0.02 0.941/0.2);--iris-a7:color(display-p3 0.004 0.02 0.847/0.279);--iris-a8:color(display-p3 0.004 0.024 0.788/0.389);--iris-a9:color(display-p3 0 0 0.706/0.644);--iris-a10:color(display-p3 0 0 0.667/0.683);--iris-a11:color(display-p3 0.337 0.326 0.748);--iris-a12:color(display-p3 0.154 0.161 0.371)}}}.dark,.dark-theme{--iris-a1:#3636fe0e;--iris-a2:#564bf916;--iris-a3:#525bff3b;--iris-a4:#4d58ff5a;--iris-a5:#5b62fd6b;--iris-a6:#6d6ffd7a;--iris-a7:#7777fe8e;--iris-a8:#7b7afeac;--iris-a9:#6a6afed4;--iris-a10:#7d79ffdc;--iris-a11:#b1a9ff;--iris-a12:#e1e0ff}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--iris-a1:color(display-p3 0.224 0.224 0.992/0.051);--iris-a2:color(display-p3 0.361 0.314 1/0.08);--iris-a3:color(display-p3 0.357 0.373 1/0.219);--iris-a4:color(display-p3 0.325 0.361 1/0.337);--iris-a5:color(display-p3 0.38 0.4 1/0.4);--iris-a6:color(display-p3 0.447 0.447 1/0.454);--iris-a7:color(display-p3 0.486 0.486 1/0.534);--iris-a8:color(display-p3 0.502 0.494 1/0.652);--iris-a9:color(display-p3 0.431 0.431 1/0.799);--iris-a10:color(display-p3 0.502 0.486 1/0.832);--iris-a11:color(display-p3 0.685 0.662 1);--iris-a12:color(display-p3 0.878 0.875 0.986)}}}.light,.light-theme,:root{--indigo-1:#fdfdfe;--indigo-2:#f7f9ff;--indigo-3:#edf2fe;--indigo-4:#e1e9ff;--indigo-5:#d2deff;--indigo-6:#c1d0ff;--indigo-7:#abbdf9;--indigo-8:#8da4ef;--indigo-9:#3e63dd;--indigo-10:#3358d4;--indigo-11:#3a5bc7;--indigo-12:#1f2d5c}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--indigo-1:color(display-p3 0.992 0.992 0.996);--indigo-2:color(display-p3 0.971 0.977 0.998);--indigo-3:color(display-p3 0.933 0.948 0.992);--indigo-4:color(display-p3 0.885 0.914 1);--indigo-5:color(display-p3 0.831 0.87 1);--indigo-6:color(display-p3 0.767 0.814 0.995);--indigo-7:color(display-p3 0.685 0.74 0.957);--indigo-8:color(display-p3 0.569 0.639 0.916);--indigo-9:color(display-p3 0.276 0.384 0.837);--indigo-10:color(display-p3 0.234 0.343 0.801);--indigo-11:color(display-p3 0.256 0.354 0.755);--indigo-12:color(display-p3 0.133 0.175 0.348)}}}.dark,.dark-theme{--indigo-1:#11131f;--indigo-2:#141726;--indigo-3:#182449;--indigo-4:#1d2e62;--indigo-5:#253974;--indigo-6:#304384;--indigo-7:#3a4f97;--indigo-8:#435db1;--indigo-9:#3e63dd;--indigo-10:#5472e4;--indigo-11:#9eb1ff;--indigo-12:#d6e1ff}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--indigo-1:color(display-p3 0.068 0.074 0.118);--indigo-2:color(display-p3 0.081 0.089 0.144);--indigo-3:color(display-p3 0.105 0.141 0.275);--indigo-4:color(display-p3 0.129 0.18 0.369);--indigo-5:color(display-p3 0.163 0.22 0.439);--indigo-6:color(display-p3 0.203 0.262 0.5);--indigo-7:color(display-p3 0.245 0.309 0.575);--indigo-8:color(display-p3 0.285 0.362 0.674);--indigo-9:color(display-p3 0.276 0.384 0.837);--indigo-10:color(display-p3 0.354 0.445 0.866);--indigo-11:color(display-p3 0.63 0.69 1);--indigo-12:color(display-p3 0.848 0.881 0.99)}}}.light,.light-theme,:root{--indigo-a1:#00008002;--indigo-a2:#0040ff08;--indigo-a3:#0047f112;--indigo-a4:#0044ff1e;--indigo-a5:#0044ff2d;--indigo-a6:#003eff3e;--indigo-a7:#0037ed54;--indigo-a8:#0034dc72;--indigo-a9:#0031d2c1;--indigo-a10:#002ec9cc;--indigo-a11:#002bb7c5;--indigo-a12:#001046e0}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--indigo-a1:color(display-p3 0.02 0.02 0.51/0.008);--indigo-a2:color(display-p3 0.024 0.161 0.863/0.028);--indigo-a3:color(display-p3 0.008 0.239 0.886/0.067);--indigo-a4:color(display-p3 0.004 0.247 1/0.114);--indigo-a5:color(display-p3 0.004 0.235 1/0.169);--indigo-a6:color(display-p3 0.004 0.208 0.984/0.232);--indigo-a7:color(display-p3 0.004 0.176 0.863/0.314);--indigo-a8:color(display-p3 0.004 0.165 0.812/0.432);--indigo-a9:color(display-p3 0 0.153 0.773/0.726);--indigo-a10:color(display-p3 0 0.137 0.737/0.765);--indigo-a11:color(display-p3 0.256 0.354 0.755);--indigo-a12:color(display-p3 0.133 0.175 0.348)}}}.dark,.dark-theme{--indigo-a1:#1133ff0f;--indigo-a2:#3354fa17;--indigo-a3:#2f62ff3c;--indigo-a4:#3566ff57;--indigo-a5:#4171fd6b;--indigo-a6:#5178fd7c;--indigo-a7:#5a7fff90;--indigo-a8:#5b81feac;--indigo-a9:#4671ffdb;--indigo-a10:#5c7efee3;--indigo-a11:#9eb1ff;--indigo-a12:#d6e1ff}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--indigo-a1:color(display-p3 0.071 0.212 0.996/0.055);--indigo-a2:color(display-p3 0.251 0.345 0.988/0.085);--indigo-a3:color(display-p3 0.243 0.404 1/0.223);--indigo-a4:color(display-p3 0.263 0.42 1/0.324);--indigo-a5:color(display-p3 0.314 0.451 1/0.4);--indigo-a6:color(display-p3 0.361 0.49 1/0.467);--indigo-a7:color(display-p3 0.388 0.51 1/0.547);--indigo-a8:color(display-p3 0.404 0.518 1/0.652);--indigo-a9:color(display-p3 0.318 0.451 1/0.824);--indigo-a10:color(display-p3 0.404 0.506 1/0.858);--indigo-a11:color(display-p3 0.63 0.69 1);--indigo-a12:color(display-p3 0.848 0.881 0.99)}}}.light,.light-theme,:root{--blue-1:#fbfdff;--blue-2:#f4faff;--blue-3:#e6f4fe;--blue-4:#d5efff;--blue-5:#c2e5ff;--blue-6:#acd8fc;--blue-7:#8ec8f6;--blue-8:#5eb1ef;--blue-9:#0090ff;--blue-10:#0588f0;--blue-11:#0d74ce;--blue-12:#113264}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--blue-1:color(display-p3 0.986 0.992 0.999);--blue-2:color(display-p3 0.96 0.979 0.998);--blue-3:color(display-p3 0.912 0.956 0.991);--blue-4:color(display-p3 0.853 0.932 1);--blue-5:color(display-p3 0.788 0.894 0.998);--blue-6:color(display-p3 0.709 0.843 0.976);--blue-7:color(display-p3 0.606 0.777 0.947);--blue-8:color(display-p3 0.451 0.688 0.917);--blue-9:color(display-p3 0.247 0.556 0.969);--blue-10:color(display-p3 0.234 0.523 0.912);--blue-11:color(display-p3 0.15 0.44 0.84);--blue-12:color(display-p3 0.102 0.193 0.379)}}}.dark,.dark-theme{--blue-1:#0d1520;--blue-2:#111927;--blue-3:#0d2847;--blue-4:#003362;--blue-5:#004074;--blue-6:#104d87;--blue-7:#205d9e;--blue-8:#2870bd;--blue-9:#0090ff;--blue-10:#3b9eff;--blue-11:#70b8ff;--blue-12:#c2e6ff}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--blue-1:color(display-p3 0.057 0.081 0.122);--blue-2:color(display-p3 0.072 0.098 0.147);--blue-3:color(display-p3 0.078 0.154 0.27);--blue-4:color(display-p3 0.033 0.197 0.37);--blue-5:color(display-p3 0.08 0.245 0.441);--blue-6:color(display-p3 0.14 0.298 0.511);--blue-7:color(display-p3 0.195 0.361 0.6);--blue-8:color(display-p3 0.239 0.434 0.72);--blue-9:color(display-p3 0.247 0.556 0.969);--blue-10:color(display-p3 0.344 0.612 0.973);--blue-11:color(display-p3 0.49 0.72 1);--blue-12:color(display-p3 0.788 0.898 0.99)}}}.light,.light-theme,:root{--blue-a1:#0080ff04;--blue-a2:#008cff0b;--blue-a3:#008ff519;--blue-a4:#009eff2a;--blue-a5:#0093ff3d;--blue-a6:#0088f653;--blue-a7:#0083eb71;--blue-a8:#0084e6a1;--blue-a9:#0090ff;--blue-a10:#0086f0fa;--blue-a11:#006dcbf2;--blue-a12:#002359ee}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--blue-a1:color(display-p3 0.024 0.514 1/0.016);--blue-a2:color(display-p3 0.024 0.514 0.906/0.04);--blue-a3:color(display-p3 0.012 0.506 0.914/0.087);--blue-a4:color(display-p3 0.008 0.545 1/0.146);--blue-a5:color(display-p3 0.004 0.502 0.984/0.212);--blue-a6:color(display-p3 0.004 0.463 0.922/0.291);--blue-a7:color(display-p3 0.004 0.431 0.863/0.393);--blue-a8:color(display-p3 0 0.427 0.851/0.55);--blue-a9:color(display-p3 0 0.412 0.961/0.753);--blue-a10:color(display-p3 0 0.376 0.886/0.765);--blue-a11:color(display-p3 0.15 0.44 0.84);--blue-a12:color(display-p3 0.102 0.193 0.379)}}}.dark,.dark-theme{--blue-a1:#004df211;--blue-a2:#1166fb18;--blue-a3:#0077ff3a;--blue-a4:#0075ff57;--blue-a5:#0081fd6b;--blue-a6:#0f89fd7f;--blue-a7:#2a91fe98;--blue-a8:#3094feb9;--blue-a9:#0090ff;--blue-a10:#3b9eff;--blue-a11:#70b8ff;--blue-a12:#c2e6ff}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--blue-a1:color(display-p3 0 0.333 1/0.059);--blue-a2:color(display-p3 0.114 0.435 0.988/0.085);--blue-a3:color(display-p3 0.122 0.463 1/0.219);--blue-a4:color(display-p3 0 0.467 1/0.324);--blue-a5:color(display-p3 0.098 0.51 1/0.4);--blue-a6:color(display-p3 0.224 0.557 1/0.475);--blue-a7:color(display-p3 0.294 0.584 1/0.572);--blue-a8:color(display-p3 0.314 0.592 1/0.702);--blue-a9:color(display-p3 0.251 0.573 0.996/0.967);--blue-a10:color(display-p3 0.357 0.631 1/0.971);--blue-a11:color(display-p3 0.49 0.72 1);--blue-a12:color(display-p3 0.788 0.898 0.99)}}}.light,.light-theme,:root{--cyan-1:#fafdfe;--cyan-2:#f2fafb;--cyan-3:#def7f9;--cyan-4:#caf1f6;--cyan-5:#b5e9f0;--cyan-6:#9ddde7;--cyan-7:#7dcedc;--cyan-8:#3db9cf;--cyan-9:#00a2c7;--cyan-10:#0797b9;--cyan-11:#107d98;--cyan-12:#0d3c48}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--cyan-1:color(display-p3 0.982 0.992 0.996);--cyan-2:color(display-p3 0.955 0.981 0.984);--cyan-3:color(display-p3 0.888 0.965 0.975);--cyan-4:color(display-p3 0.821 0.941 0.959);--cyan-5:color(display-p3 0.751 0.907 0.935);--cyan-6:color(display-p3 0.671 0.862 0.9);--cyan-7:color(display-p3 0.564 0.8 0.854);--cyan-8:color(display-p3 0.388 0.715 0.798);--cyan-9:color(display-p3 0.282 0.627 0.765);--cyan-10:color(display-p3 0.264 0.583 0.71);--cyan-11:color(display-p3 0.08 0.48 0.63);--cyan-12:color(display-p3 0.108 0.232 0.277)}}}.dark,.dark-theme{--cyan-1:#0b161a;--cyan-2:#101b20;--cyan-3:#082c36;--cyan-4:#003848;--cyan-5:#004558;--cyan-6:#045468;--cyan-7:#12677e;--cyan-8:#11809c;--cyan-9:#00a2c7;--cyan-10:#23afd0;--cyan-11:#4ccce6;--cyan-12:#b6ecf7}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--cyan-1:color(display-p3 0.053 0.085 0.098);--cyan-2:color(display-p3 0.072 0.105 0.122);--cyan-3:color(display-p3 0.073 0.168 0.209);--cyan-4:color(display-p3 0.063 0.216 0.277);--cyan-5:color(display-p3 0.091 0.267 0.336);--cyan-6:color(display-p3 0.137 0.324 0.4);--cyan-7:color(display-p3 0.186 0.398 0.484);--cyan-8:color(display-p3 0.23 0.496 0.6);--cyan-9:color(display-p3 0.282 0.627 0.765);--cyan-10:color(display-p3 0.331 0.675 0.801);--cyan-11:color(display-p3 0.446 0.79 0.887);--cyan-12:color(display-p3 0.757 0.919 0.962)}}}.light,.light-theme,:root{--cyan-a1:#0099cc05;--cyan-a2:#009db10d;--cyan-a3:#00c2d121;--cyan-a4:#00bcd435;--cyan-a5:#01b4cc4a;--cyan-a6:#00a7c162;--cyan-a7:#009fbb82;--cyan-a8:#00a3c0c2;--cyan-a9:#00a2c7;--cyan-a10:#0094b7f8;--cyan-a11:#007491ef;--cyan-a12:#00323ef2}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--cyan-a1:color(display-p3 0.02 0.608 0.804/0.02);--cyan-a2:color(display-p3 0.02 0.557 0.647/0.044);--cyan-a3:color(display-p3 0.004 0.694 0.796/0.114);--cyan-a4:color(display-p3 0.004 0.678 0.784/0.181);--cyan-a5:color(display-p3 0.004 0.624 0.733/0.248);--cyan-a6:color(display-p3 0.004 0.584 0.706/0.33);--cyan-a7:color(display-p3 0.004 0.541 0.667/0.436);--cyan-a8:color(display-p3 0 0.533 0.667/0.612);--cyan-a9:color(display-p3 0 0.482 0.675/0.718);--cyan-a10:color(display-p3 0 0.435 0.608/0.738);--cyan-a11:color(display-p3 0.08 0.48 0.63);--cyan-a12:color(display-p3 0.108 0.232 0.277)}}}.dark,.dark-theme{--cyan-a1:#0091f70a;--cyan-a2:#02a7f211;--cyan-a3:#00befd28;--cyan-a4:#00baff3b;--cyan-a5:#00befd4d;--cyan-a6:#00c7fd5e;--cyan-a7:#14cdff75;--cyan-a8:#11cfff95;--cyan-a9:#00cfffc3;--cyan-a10:#28d6ffcd;--cyan-a11:#52e1fee5;--cyan-a12:#bbf3fef7}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--cyan-a1:color(display-p3 0 0.647 0.992/0.034);--cyan-a2:color(display-p3 0.133 0.733 1/0.059);--cyan-a3:color(display-p3 0.122 0.741 0.996/0.152);--cyan-a4:color(display-p3 0.051 0.725 1/0.227);--cyan-a5:color(display-p3 0.149 0.757 1/0.29);--cyan-a6:color(display-p3 0.267 0.792 1/0.358);--cyan-a7:color(display-p3 0.333 0.808 1/0.446);--cyan-a8:color(display-p3 0.357 0.816 1/0.572);--cyan-a9:color(display-p3 0.357 0.82 1/0.748);--cyan-a10:color(display-p3 0.4 0.839 1/0.786);--cyan-a11:color(display-p3 0.446 0.79 0.887);--cyan-a12:color(display-p3 0.757 0.919 0.962)}}}.light,.light-theme,:root{--teal-1:#fafefd;--teal-2:#f3fbf9;--teal-3:#e0f8f3;--teal-4:#ccf3ea;--teal-5:#b8eae0;--teal-6:#a1ded2;--teal-7:#83cdc1;--teal-8:#53b9ab;--teal-9:#12a594;--teal-10:#0d9b8a;--teal-11:#008573;--teal-12:#0d3d38}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--teal-1:color(display-p3 0.983 0.996 0.992);--teal-2:color(display-p3 0.958 0.983 0.976);--teal-3:color(display-p3 0.895 0.971 0.952);--teal-4:color(display-p3 0.831 0.949 0.92);--teal-5:color(display-p3 0.761 0.914 0.878);--teal-6:color(display-p3 0.682 0.864 0.825);--teal-7:color(display-p3 0.581 0.798 0.756);--teal-8:color(display-p3 0.433 0.716 0.671);--teal-9:color(display-p3 0.297 0.637 0.581);--teal-10:color(display-p3 0.275 0.599 0.542);--teal-11:color(display-p3 0.08 0.5 0.43);--teal-12:color(display-p3 0.11 0.235 0.219)}}}.dark,.dark-theme{--teal-1:#0d1514;--teal-2:#111c1b;--teal-3:#0d2d2a;--teal-4:#023b37;--teal-5:#084843;--teal-6:#145750;--teal-7:#1c6961;--teal-8:#207e73;--teal-9:#12a594;--teal-10:#0eb39e;--teal-11:#0bd8b6;--teal-12:#adf0dd}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--teal-1:color(display-p3 0.059 0.083 0.079);--teal-2:color(display-p3 0.075 0.11 0.107);--teal-3:color(display-p3 0.087 0.175 0.165);--teal-4:color(display-p3 0.087 0.227 0.214);--teal-5:color(display-p3 0.12 0.277 0.261);--teal-6:color(display-p3 0.162 0.335 0.314);--teal-7:color(display-p3 0.205 0.406 0.379);--teal-8:color(display-p3 0.245 0.489 0.453);--teal-9:color(display-p3 0.297 0.637 0.581);--teal-10:color(display-p3 0.319 0.69 0.62);--teal-11:color(display-p3 0.388 0.835 0.719);--teal-12:color(display-p3 0.734 0.934 0.87)}}}.light,.light-theme,:root{--teal-a1:#00cc9905;--teal-a2:#00aa800c;--teal-a3:#00c69d1f;--teal-a4:#00c39633;--teal-a5:#00b49047;--teal-a6:#00a6855e;--teal-a7:#0099807c;--teal-a8:#009783ac;--teal-a9:#009e8ced;--teal-a10:#009684f2;--teal-a11:#008573;--teal-a12:#00332df2}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--teal-a1:color(display-p3 0.024 0.757 0.514/0.016);--teal-a2:color(display-p3 0.02 0.647 0.467/0.044);--teal-a3:color(display-p3 0.004 0.741 0.557/0.106);--teal-a4:color(display-p3 0.004 0.702 0.537/0.169);--teal-a5:color(display-p3 0.004 0.643 0.494/0.24);--teal-a6:color(display-p3 0.004 0.569 0.447/0.318);--teal-a7:color(display-p3 0.004 0.518 0.424/0.42);--teal-a8:color(display-p3 0 0.506 0.424/0.569);--teal-a9:color(display-p3 0 0.482 0.404/0.702);--teal-a10:color(display-p3 0 0.451 0.369/0.726);--teal-a11:color(display-p3 0.08 0.5 0.43);--teal-a12:color(display-p3 0.11 0.235 0.219)}}}.dark,.dark-theme{--teal-a1:#00deab05;--teal-a2:#12fbe60c;--teal-a3:#00ffe61e;--teal-a4:#00ffe92d;--teal-a5:#00ffea3b;--teal-a6:#1cffe84b;--teal-a7:#2efde85f;--teal-a8:#32ffe775;--teal-a9:#13ffe49f;--teal-a10:#0dffe0ae;--teal-a11:#0afed5d6;--teal-a12:#b8ffebef}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--teal-a1:color(display-p3 0 0.992 0.761/0.017);--teal-a2:color(display-p3 0.235 0.988 0.902/0.047);--teal-a3:color(display-p3 0.235 1 0.898/0.118);--teal-a4:color(display-p3 0.18 0.996 0.929/0.173);--teal-a5:color(display-p3 0.31 1 0.933/0.227);--teal-a6:color(display-p3 0.396 1 0.933/0.286);--teal-a7:color(display-p3 0.443 1 0.925/0.366);--teal-a8:color(display-p3 0.459 1 0.925/0.454);--teal-a9:color(display-p3 0.443 0.996 0.906/0.61);--teal-a10:color(display-p3 0.439 0.996 0.89/0.669);--teal-a11:color(display-p3 0.388 0.835 0.719);--teal-a12:color(display-p3 0.734 0.934 0.87)}}}.light,.light-theme,:root{--jade-1:#fbfefd;--jade-2:#f4fbf7;--jade-3:#e6f7ed;--jade-4:#d6f1e3;--jade-5:#c3e9d7;--jade-6:#acdec8;--jade-7:#8bceb6;--jade-8:#56ba9f;--jade-9:#29a383;--jade-10:#26997b;--jade-11:#208368;--jade-12:#1d3b31}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--jade-1:color(display-p3 0.986 0.996 0.992);--jade-2:color(display-p3 0.962 0.983 0.969);--jade-3:color(display-p3 0.912 0.965 0.932);--jade-4:color(display-p3 0.858 0.941 0.893);--jade-5:color(display-p3 0.795 0.909 0.847);--jade-6:color(display-p3 0.715 0.864 0.791);--jade-7:color(display-p3 0.603 0.802 0.718);--jade-8:color(display-p3 0.44 0.72 0.629);--jade-9:color(display-p3 0.319 0.63 0.521);--jade-10:color(display-p3 0.299 0.592 0.488);--jade-11:color(display-p3 0.15 0.5 0.37);--jade-12:color(display-p3 0.142 0.229 0.194)}}}.dark,.dark-theme{--jade-1:#0d1512;--jade-2:#121c18;--jade-3:#0f2e22;--jade-4:#0b3b2c;--jade-5:#114837;--jade-6:#1b5745;--jade-7:#246854;--jade-8:#2a7e68;--jade-9:#29a383;--jade-10:#27b08b;--jade-11:#1fd8a4;--jade-12:#adf0d4}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--jade-1:color(display-p3 0.059 0.083 0.071);--jade-2:color(display-p3 0.078 0.11 0.094);--jade-3:color(display-p3 0.091 0.176 0.138);--jade-4:color(display-p3 0.102 0.228 0.177);--jade-5:color(display-p3 0.133 0.279 0.221);--jade-6:color(display-p3 0.174 0.334 0.273);--jade-7:color(display-p3 0.219 0.402 0.335);--jade-8:color(display-p3 0.263 0.488 0.411);--jade-9:color(display-p3 0.319 0.63 0.521);--jade-10:color(display-p3 0.338 0.68 0.555);--jade-11:color(display-p3 0.4 0.835 0.656);--jade-12:color(display-p3 0.734 0.934 0.838)}}}.light,.light-theme,:root{--jade-a1:#00c08004;--jade-a2:#00a3460b;--jade-a3:#00ae4819;--jade-a4:#00a85129;--jade-a5:#00a2553c;--jade-a6:#009a5753;--jade-a7:#00945f74;--jade-a8:#00976ea9;--jade-a9:#00916bd6;--jade-a10:#008764d9;--jade-a11:#007152df;--jade-a12:#002217e2}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--jade-a1:color(display-p3 0.024 0.757 0.514/0.016);--jade-a2:color(display-p3 0.024 0.612 0.22/0.04);--jade-a3:color(display-p3 0.012 0.596 0.235/0.087);--jade-a4:color(display-p3 0.008 0.588 0.255/0.142);--jade-a5:color(display-p3 0.004 0.561 0.251/0.204);--jade-a6:color(display-p3 0.004 0.525 0.278/0.287);--jade-a7:color(display-p3 0.004 0.506 0.29/0.397);--jade-a8:color(display-p3 0 0.506 0.337/0.561);--jade-a9:color(display-p3 0 0.459 0.298/0.683);--jade-a10:color(display-p3 0 0.42 0.271/0.702);--jade-a11:color(display-p3 0.15 0.5 0.37);--jade-a12:color(display-p3 0.142 0.229 0.194)}}}.dark,.dark-theme{--jade-a1:#00de4505;--jade-a2:#27fba60c;--jade-a3:#02f99920;--jade-a4:#00ffaa2d;--jade-a5:#11ffb63b;--jade-a6:#34ffc24b;--jade-a7:#45fdc75e;--jade-a8:#48ffcf75;--jade-a9:#38feca9d;--jade-a10:#31fec7ab;--jade-a11:#21fec0d6;--jade-a12:#b8ffe1ef}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--jade-a1:color(display-p3 0 0.992 0.298/0.017);--jade-a2:color(display-p3 0.318 0.988 0.651/0.047);--jade-a3:color(display-p3 0.267 1 0.667/0.118);--jade-a4:color(display-p3 0.275 0.996 0.702/0.173);--jade-a5:color(display-p3 0.361 1 0.741/0.227);--jade-a6:color(display-p3 0.439 1 0.796/0.286);--jade-a7:color(display-p3 0.49 1 0.804/0.362);--jade-a8:color(display-p3 0.506 1 0.835/0.45);--jade-a9:color(display-p3 0.478 0.996 0.816/0.606);--jade-a10:color(display-p3 0.478 1 0.816/0.656);--jade-a11:color(display-p3 0.4 0.835 0.656);--jade-a12:color(display-p3 0.734 0.934 0.838)}}}.light,.light-theme,:root{--green-1:#fbfefc;--green-2:#f4fbf6;--green-3:#e6f6eb;--green-4:#d6f1df;--green-5:#c4e8d1;--green-6:#adddc0;--green-7:#8eceaa;--green-8:#5bb98b;--green-9:#30a46c;--green-10:#2b9a66;--green-11:#218358;--green-12:#193b2d}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--green-1:color(display-p3 0.986 0.996 0.989);--green-2:color(display-p3 0.963 0.983 0.967);--green-3:color(display-p3 0.913 0.964 0.925);--green-4:color(display-p3 0.859 0.94 0.879);--green-5:color(display-p3 0.796 0.907 0.826);--green-6:color(display-p3 0.718 0.863 0.761);--green-7:color(display-p3 0.61 0.801 0.675);--green-8:color(display-p3 0.451 0.715 0.559);--green-9:color(display-p3 0.332 0.634 0.442);--green-10:color(display-p3 0.308 0.595 0.417);--green-11:color(display-p3 0.19 0.5 0.32);--green-12:color(display-p3 0.132 0.228 0.18)}}}.dark,.dark-theme{--green-1:#0e1512;--green-2:#121b17;--green-3:#132d21;--green-4:#113b29;--green-5:#174933;--green-6:#20573e;--green-7:#28684a;--green-8:#2f7c57;--green-9:#30a46c;--green-10:#33b074;--green-11:#3dd68c;--green-12:#b1f1cb}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--green-1:color(display-p3 0.062 0.083 0.071);--green-2:color(display-p3 0.079 0.106 0.09);--green-3:color(display-p3 0.1 0.173 0.133);--green-4:color(display-p3 0.115 0.229 0.166);--green-5:color(display-p3 0.147 0.282 0.206);--green-6:color(display-p3 0.185 0.338 0.25);--green-7:color(display-p3 0.227 0.403 0.298);--green-8:color(display-p3 0.27 0.479 0.351);--green-9:color(display-p3 0.332 0.634 0.442);--green-10:color(display-p3 0.357 0.682 0.474);--green-11:color(display-p3 0.434 0.828 0.573);--green-12:color(display-p3 0.747 0.938 0.807)}}}.light,.light-theme,:root{--green-a1:#00c04004;--green-a2:#00a32f0b;--green-a3:#00a43319;--green-a4:#00a83829;--green-a5:#019c393b;--green-a6:#00963c52;--green-a7:#00914071;--green-a8:#00924ba4;--green-a9:#008f4acf;--green-a10:#008647d4;--green-a11:#00713fde;--green-a12:#002616e6}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--green-a1:color(display-p3 0.024 0.757 0.267/0.016);--green-a2:color(display-p3 0.024 0.565 0.129/0.036);--green-a3:color(display-p3 0.012 0.596 0.145/0.087);--green-a4:color(display-p3 0.008 0.588 0.145/0.142);--green-a5:color(display-p3 0.004 0.541 0.157/0.204);--green-a6:color(display-p3 0.004 0.518 0.157/0.283);--green-a7:color(display-p3 0.004 0.486 0.165/0.389);--green-a8:color(display-p3 0 0.478 0.2/0.55);--green-a9:color(display-p3 0 0.455 0.165/0.667);--green-a10:color(display-p3 0 0.416 0.153/0.691);--green-a11:color(display-p3 0.19 0.5 0.32);--green-a12:color(display-p3 0.132 0.228 0.18)}}}.dark,.dark-theme{--green-a1:#00de4505;--green-a2:#29f99d0b;--green-a3:#22ff991e;--green-a4:#11ff992d;--green-a5:#2bffa23c;--green-a6:#44ffaa4b;--green-a7:#50fdac5e;--green-a8:#54ffad73;--green-a9:#44ffa49e;--green-a10:#43fea4ab;--green-a11:#46fea5d4;--green-a12:#bbffd7f0}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--green-a1:color(display-p3 0 0.992 0.298/0.017);--green-a2:color(display-p3 0.341 0.98 0.616/0.043);--green-a3:color(display-p3 0.376 0.996 0.655/0.114);--green-a4:color(display-p3 0.341 0.996 0.635/0.173);--green-a5:color(display-p3 0.408 1 0.678/0.232);--green-a6:color(display-p3 0.475 1 0.706/0.29);--green-a7:color(display-p3 0.514 1 0.706/0.362);--green-a8:color(display-p3 0.529 1 0.718/0.442);--green-a9:color(display-p3 0.502 0.996 0.682/0.61);--green-a10:color(display-p3 0.506 1 0.682/0.66);--green-a11:color(display-p3 0.434 0.828 0.573);--green-a12:color(display-p3 0.747 0.938 0.807)}}}.light,.light-theme,:root{--grass-1:#fbfefb;--grass-2:#f5fbf5;--grass-3:#e9f6e9;--grass-4:#daf1db;--grass-5:#c9e8ca;--grass-6:#b2ddb5;--grass-7:#94ce9a;--grass-8:#65ba74;--grass-9:#46a758;--grass-10:#3e9b4f;--grass-11:#2a7e3b;--grass-12:#203c25}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--grass-1:color(display-p3 0.986 0.996 0.985);--grass-2:color(display-p3 0.966 0.983 0.964);--grass-3:color(display-p3 0.923 0.965 0.917);--grass-4:color(display-p3 0.872 0.94 0.865);--grass-5:color(display-p3 0.811 0.908 0.802);--grass-6:color(display-p3 0.733 0.864 0.724);--grass-7:color(display-p3 0.628 0.803 0.622);--grass-8:color(display-p3 0.477 0.72 0.482);--grass-9:color(display-p3 0.38 0.647 0.378);--grass-10:color(display-p3 0.344 0.598 0.342);--grass-11:color(display-p3 0.263 0.488 0.261);--grass-12:color(display-p3 0.151 0.233 0.153)}}}.dark,.dark-theme{--grass-1:#0e1511;--grass-2:#141a15;--grass-3:#1b2a1e;--grass-4:#1d3a24;--grass-5:#25482d;--grass-6:#2d5736;--grass-7:#366740;--grass-8:#3e7949;--grass-9:#46a758;--grass-10:#53b365;--grass-11:#71d083;--grass-12:#c2f0c2}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--grass-1:color(display-p3 0.062 0.083 0.067);--grass-2:color(display-p3 0.083 0.103 0.085);--grass-3:color(display-p3 0.118 0.163 0.122);--grass-4:color(display-p3 0.142 0.225 0.15);--grass-5:color(display-p3 0.178 0.279 0.186);--grass-6:color(display-p3 0.217 0.337 0.224);--grass-7:color(display-p3 0.258 0.4 0.264);--grass-8:color(display-p3 0.302 0.47 0.305);--grass-9:color(display-p3 0.38 0.647 0.378);--grass-10:color(display-p3 0.426 0.694 0.426);--grass-11:color(display-p3 0.535 0.807 0.542);--grass-12:color(display-p3 0.797 0.936 0.776)}}}.light,.light-theme,:root{--grass-a1:#00c00004;--grass-a2:#0099000a;--grass-a3:#00970016;--grass-a4:#009f0725;--grass-a5:#00930536;--grass-a6:#008f0a4d;--grass-a7:#018b0f6b;--grass-a8:#008d199a;--grass-a9:#008619b9;--grass-a10:#007b17c1;--grass-a11:#006514d5;--grass-a12:#002006df}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--grass-a1:color(display-p3 0.024 0.757 0.024/0.016);--grass-a2:color(display-p3 0.024 0.565 0.024/0.036);--grass-a3:color(display-p3 0.059 0.576 0.008/0.083);--grass-a4:color(display-p3 0.035 0.565 0.008/0.134);--grass-a5:color(display-p3 0.047 0.545 0.008/0.197);--grass-a6:color(display-p3 0.031 0.502 0.004/0.275);--grass-a7:color(display-p3 0.012 0.482 0.004/0.377);--grass-a8:color(display-p3 0 0.467 0.008/0.522);--grass-a9:color(display-p3 0.008 0.435 0/0.624);--grass-a10:color(display-p3 0.008 0.388 0/0.659);--grass-a11:color(display-p3 0.263 0.488 0.261);--grass-a12:color(display-p3 0.151 0.233 0.153)}}}.dark,.dark-theme{--grass-a1:#00de1205;--grass-a2:#5ef7780a;--grass-a3:#70fe8c1b;--grass-a4:#57ff802c;--grass-a5:#68ff8b3b;--grass-a6:#71ff8f4b;--grass-a7:#77fd925d;--grass-a8:#77fd9070;--grass-a9:#65ff82a1;--grass-a10:#72ff8dae;--grass-a11:#89ff9fcd;--grass-a12:#ceffceef}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--grass-a1:color(display-p3 0 0.992 0.071/0.017);--grass-a2:color(display-p3 0.482 0.996 0.584/0.038);--grass-a3:color(display-p3 0.549 0.992 0.588/0.106);--grass-a4:color(display-p3 0.51 0.996 0.557/0.169);--grass-a5:color(display-p3 0.553 1 0.588/0.227);--grass-a6:color(display-p3 0.584 1 0.608/0.29);--grass-a7:color(display-p3 0.604 1 0.616/0.358);--grass-a8:color(display-p3 0.608 1 0.62/0.433);--grass-a9:color(display-p3 0.573 1 0.569/0.622);--grass-a10:color(display-p3 0.6 0.996 0.6/0.673);--grass-a11:color(display-p3 0.535 0.807 0.542);--grass-a12:color(display-p3 0.797 0.936 0.776)}}}.light,.light-theme,:root{--orange-1:#fefcfb;--orange-2:#fff7ed;--orange-3:#ffefd6;--orange-4:#ffdfb5;--orange-5:#ffd19a;--orange-6:#ffc182;--orange-7:#f5ae73;--orange-8:#ec9455;--orange-9:#f76b15;--orange-10:#ef5f00;--orange-11:#cc4e00;--orange-12:#582d1d}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--orange-1:color(display-p3 0.995 0.988 0.985);--orange-2:color(display-p3 0.994 0.968 0.934);--orange-3:color(display-p3 0.989 0.938 0.85);--orange-4:color(display-p3 1 0.874 0.687);--orange-5:color(display-p3 1 0.821 0.583);--orange-6:color(display-p3 0.975 0.767 0.545);--orange-7:color(display-p3 0.919 0.693 0.486);--orange-8:color(display-p3 0.877 0.597 0.379);--orange-9:color(display-p3 0.9 0.45 0.2);--orange-10:color(display-p3 0.87 0.409 0.164);--orange-11:color(display-p3 0.76 0.34 0);--orange-12:color(display-p3 0.323 0.185 0.127)}}}.dark,.dark-theme{--orange-1:#17120e;--orange-2:#1e160f;--orange-3:#331e0b;--orange-4:#462100;--orange-5:#562800;--orange-6:#66350c;--orange-7:#7e451d;--orange-8:#a35829;--orange-9:#f76b15;--orange-10:#ff801f;--orange-11:#ffa057;--orange-12:#ffe0c2}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--orange-1:color(display-p3 0.088 0.07 0.057);--orange-2:color(display-p3 0.113 0.089 0.061);--orange-3:color(display-p3 0.189 0.12 0.056);--orange-4:color(display-p3 0.262 0.132 0);--orange-5:color(display-p3 0.315 0.168 0.016);--orange-6:color(display-p3 0.376 0.219 0.088);--orange-7:color(display-p3 0.465 0.283 0.147);--orange-8:color(display-p3 0.601 0.359 0.201);--orange-9:color(display-p3 0.9 0.45 0.2);--orange-10:color(display-p3 0.98 0.51 0.23);--orange-11:color(display-p3 1 0.63 0.38);--orange-12:color(display-p3 0.98 0.883 0.775)}}}.light,.light-theme,:root{--orange-a1:#c0400004;--orange-a2:#ff8e0012;--orange-a3:#ff9c0029;--orange-a4:#ff91014a;--orange-a5:#ff8b0065;--orange-a6:#ff81007d;--orange-a7:#ed6c008c;--orange-a8:#e35f00aa;--orange-a9:#f65e00ea;--orange-a10:#ef5f00;--orange-a11:#cc4e00;--orange-a12:#431200e2}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--orange-a1:color(display-p3 0.757 0.267 0.024/0.016);--orange-a2:color(display-p3 0.886 0.533 0.008/0.067);--orange-a3:color(display-p3 0.922 0.584 0.008/0.15);--orange-a4:color(display-p3 1 0.604 0.004/0.314);--orange-a5:color(display-p3 1 0.569 0.004/0.416);--orange-a6:color(display-p3 0.949 0.494 0.004/0.455);--orange-a7:color(display-p3 0.839 0.408 0/0.514);--orange-a8:color(display-p3 0.804 0.349 0/0.62);--orange-a9:color(display-p3 0.878 0.314 0/0.8);--orange-a10:color(display-p3 0.843 0.29 0/0.836);--orange-a11:color(display-p3 0.76 0.34 0);--orange-a12:color(display-p3 0.323 0.185 0.127)}}}.dark,.dark-theme{--orange-a1:#ec360007;--orange-a2:#fe6d000e;--orange-a3:#fb6a0025;--orange-a4:#ff590039;--orange-a5:#ff61004a;--orange-a6:#fd75045c;--orange-a7:#ff832c75;--orange-a8:#fe84389d;--orange-a9:#fe6d15f7;--orange-a10:#ff801f;--orange-a11:#ffa057;--orange-a12:#ffe0c2}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--orange-a1:color(display-p3 0.961 0.247 0/0.022);--orange-a2:color(display-p3 0.992 0.529 0/0.051);--orange-a3:color(display-p3 0.996 0.486 0/0.131);--orange-a4:color(display-p3 0.996 0.384 0/0.211);--orange-a5:color(display-p3 1 0.455 0/0.265);--orange-a6:color(display-p3 1 0.529 0.129/0.332);--orange-a7:color(display-p3 1 0.569 0.251/0.429);--orange-a8:color(display-p3 1 0.584 0.302/0.572);--orange-a9:color(display-p3 1 0.494 0.216/0.895);--orange-a10:color(display-p3 1 0.522 0.235/0.979);--orange-a11:color(display-p3 1 0.63 0.38);--orange-a12:color(display-p3 0.98 0.883 0.775)}}}.light,.light-theme,:root{--brown-1:#fefdfc;--brown-2:#fcf9f6;--brown-3:#f6eee7;--brown-4:#f0e4d9;--brown-5:#ebdaca;--brown-6:#e4cdb7;--brown-7:#dcbc9f;--brown-8:#cea37e;--brown-9:#ad7f58;--brown-10:#a07553;--brown-11:#815e46;--brown-12:#3e332e}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--brown-1:color(display-p3 0.995 0.992 0.989);--brown-2:color(display-p3 0.987 0.976 0.964);--brown-3:color(display-p3 0.959 0.936 0.909);--brown-4:color(display-p3 0.934 0.897 0.855);--brown-5:color(display-p3 0.909 0.856 0.798);--brown-6:color(display-p3 0.88 0.808 0.73);--brown-7:color(display-p3 0.841 0.742 0.639);--brown-8:color(display-p3 0.782 0.647 0.514);--brown-9:color(display-p3 0.651 0.505 0.368);--brown-10:color(display-p3 0.601 0.465 0.344);--brown-11:color(display-p3 0.485 0.374 0.288);--brown-12:color(display-p3 0.236 0.202 0.183)}}}.dark,.dark-theme{--brown-1:#12110f;--brown-2:#1c1816;--brown-3:#28211d;--brown-4:#322922;--brown-5:#3e3128;--brown-6:#4d3c2f;--brown-7:#614a39;--brown-8:#7c5f46;--brown-9:#ad7f58;--brown-10:#b88c67;--brown-11:#dbb594;--brown-12:#f2e1ca}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--brown-1:color(display-p3 0.071 0.067 0.059);--brown-2:color(display-p3 0.107 0.095 0.087);--brown-3:color(display-p3 0.151 0.13 0.115);--brown-4:color(display-p3 0.191 0.161 0.138);--brown-5:color(display-p3 0.235 0.194 0.162);--brown-6:color(display-p3 0.291 0.237 0.192);--brown-7:color(display-p3 0.365 0.295 0.232);--brown-8:color(display-p3 0.469 0.377 0.287);--brown-9:color(display-p3 0.651 0.505 0.368);--brown-10:color(display-p3 0.697 0.557 0.423);--brown-11:color(display-p3 0.835 0.715 0.597);--brown-12:color(display-p3 0.938 0.885 0.802)}}}.light,.light-theme,:root{--brown-a1:#aa550003;--brown-a2:#aa550009;--brown-a3:#a04b0018;--brown-a4:#9b4a0026;--brown-a5:#9f4d0035;--brown-a6:#a04e0048;--brown-a7:#a34e0060;--brown-a8:#9f4a0081;--brown-a9:#823c00a7;--brown-a10:#723300ac;--brown-a11:#522100b9;--brown-a12:#140600d1}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--brown-a1:color(display-p3 0.675 0.349 0.024/0.012);--brown-a2:color(display-p3 0.675 0.349 0.024/0.036);--brown-a3:color(display-p3 0.573 0.314 0.012/0.091);--brown-a4:color(display-p3 0.545 0.302 0.008/0.146);--brown-a5:color(display-p3 0.561 0.29 0.004/0.204);--brown-a6:color(display-p3 0.553 0.294 0.004/0.271);--brown-a7:color(display-p3 0.557 0.286 0.004/0.361);--brown-a8:color(display-p3 0.549 0.275 0.004/0.487);--brown-a9:color(display-p3 0.447 0.22 0/0.632);--brown-a10:color(display-p3 0.388 0.188 0/0.655);--brown-a11:color(display-p3 0.485 0.374 0.288);--brown-a12:color(display-p3 0.236 0.202 0.183)}}}.dark,.dark-theme{--brown-a1:#91110002;--brown-a2:#fba67c0c;--brown-a3:#fcb58c19;--brown-a4:#fbbb8a24;--brown-a5:#fcb88931;--brown-a6:#fdba8741;--brown-a7:#ffbb8856;--brown-a8:#ffbe8773;--brown-a9:#feb87da8;--brown-a10:#ffc18cb3;--brown-a11:#fed1aad9;--brown-a12:#feecd4f2}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--brown-a1:color(display-p3 0.855 0.071 0/0.005);--brown-a2:color(display-p3 0.98 0.706 0.525/0.043);--brown-a3:color(display-p3 0.996 0.745 0.576/0.093);--brown-a4:color(display-p3 1 0.765 0.592/0.135);--brown-a5:color(display-p3 1 0.761 0.588/0.181);--brown-a6:color(display-p3 1 0.773 0.592/0.24);--brown-a7:color(display-p3 0.996 0.776 0.58/0.32);--brown-a8:color(display-p3 1 0.78 0.573/0.433);--brown-a9:color(display-p3 1 0.769 0.549/0.627);--brown-a10:color(display-p3 1 0.792 0.596/0.677);--brown-a11:color(display-p3 0.835 0.715 0.597);--brown-a12:color(display-p3 0.938 0.885 0.802)}}}.light,.light-theme,:root{--sky-1:#f9feff;--sky-2:#f1fafd;--sky-3:#e1f6fd;--sky-4:#d1f0fa;--sky-5:#bee7f5;--sky-6:#a9daed;--sky-7:#8dcae3;--sky-8:#60b3d7;--sky-9:#7ce2fe;--sky-10:#74daf8;--sky-11:#00749e;--sky-12:#1d3e56}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--sky-1:color(display-p3 0.98 0.995 0.999);--sky-2:color(display-p3 0.953 0.98 0.99);--sky-3:color(display-p3 0.899 0.963 0.989);--sky-4:color(display-p3 0.842 0.937 0.977);--sky-5:color(display-p3 0.777 0.9 0.954);--sky-6:color(display-p3 0.701 0.851 0.921);--sky-7:color(display-p3 0.604 0.785 0.879);--sky-8:color(display-p3 0.457 0.696 0.829);--sky-9:color(display-p3 0.585 0.877 0.983);--sky-10:color(display-p3 0.555 0.845 0.959);--sky-11:color(display-p3 0.193 0.448 0.605);--sky-12:color(display-p3 0.145 0.241 0.329)}}}.dark,.dark-theme{--sky-1:#0d141f;--sky-2:#111a27;--sky-3:#112840;--sky-4:#113555;--sky-5:#154467;--sky-6:#1b537b;--sky-7:#1f6692;--sky-8:#197cae;--sky-9:#7ce2fe;--sky-10:#a8eeff;--sky-11:#75c7f0;--sky-12:#c2f3ff}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--sky-1:color(display-p3 0.056 0.078 0.116);--sky-2:color(display-p3 0.075 0.101 0.149);--sky-3:color(display-p3 0.089 0.154 0.244);--sky-4:color(display-p3 0.106 0.207 0.323);--sky-5:color(display-p3 0.135 0.261 0.394);--sky-6:color(display-p3 0.17 0.322 0.469);--sky-7:color(display-p3 0.205 0.394 0.557);--sky-8:color(display-p3 0.232 0.48 0.665);--sky-9:color(display-p3 0.585 0.877 0.983);--sky-10:color(display-p3 0.718 0.925 0.991);--sky-11:color(display-p3 0.536 0.772 0.924);--sky-12:color(display-p3 0.799 0.947 0.993)}}}.light,.light-theme,:root{--sky-a1:#00d5ff06;--sky-a2:#00a4db0e;--sky-a3:#00b3ee1e;--sky-a4:#00ace42e;--sky-a5:#00a1d841;--sky-a6:#0092ca56;--sky-a7:#0089c172;--sky-a8:#0085bf9f;--sky-a9:#00c7fe83;--sky-a10:#00bcf38b;--sky-a11:#00749e;--sky-a12:#002540e2}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--sky-a1:color(display-p3 0.02 0.804 1/0.02);--sky-a2:color(display-p3 0.024 0.592 0.757/0.048);--sky-a3:color(display-p3 0.004 0.655 0.886/0.102);--sky-a4:color(display-p3 0.004 0.604 0.851/0.157);--sky-a5:color(display-p3 0.004 0.565 0.792/0.224);--sky-a6:color(display-p3 0.004 0.502 0.737/0.299);--sky-a7:color(display-p3 0.004 0.459 0.694/0.397);--sky-a8:color(display-p3 0 0.435 0.682/0.542);--sky-a9:color(display-p3 0.004 0.71 0.965/0.416);--sky-a10:color(display-p3 0.004 0.647 0.914/0.444);--sky-a11:color(display-p3 0.193 0.448 0.605);--sky-a12:color(display-p3 0.145 0.241 0.329)}}}.dark,.dark-theme{--sky-a1:#0044ff0f;--sky-a2:#1171fb18;--sky-a3:#1184fc33;--sky-a4:#128fff49;--sky-a5:#1c9dfd5d;--sky-a6:#28a5ff72;--sky-a7:#2badfe8b;--sky-a8:#1db2fea9;--sky-a9:#7ce3ff;--sky-a10:#a8eeff;--sky-a11:#7cd3ffef;--sky-a12:#c2f3ff}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--sky-a1:color(display-p3 0 0.282 0.996/0.055);--sky-a2:color(display-p3 0.157 0.467 0.992/0.089);--sky-a3:color(display-p3 0.192 0.522 0.996/0.19);--sky-a4:color(display-p3 0.212 0.584 1/0.274);--sky-a5:color(display-p3 0.259 0.631 1/0.349);--sky-a6:color(display-p3 0.302 0.655 1/0.433);--sky-a7:color(display-p3 0.329 0.686 1/0.526);--sky-a8:color(display-p3 0.325 0.71 1/0.643);--sky-a9:color(display-p3 0.592 0.894 1/0.984);--sky-a10:color(display-p3 0.722 0.933 1/0.992);--sky-a11:color(display-p3 0.536 0.772 0.924);--sky-a12:color(display-p3 0.799 0.947 0.993)}}}.light,.light-theme,:root{--mint-1:#f9fefd;--mint-2:#f2fbf9;--mint-3:#ddf9f2;--mint-4:#c8f4e9;--mint-5:#b3ecde;--mint-6:#9ce0d0;--mint-7:#7ecfbd;--mint-8:#4cbba5;--mint-9:#86ead4;--mint-10:#7de0cb;--mint-11:#027864;--mint-12:#16433c}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--mint-1:color(display-p3 0.98 0.995 0.992);--mint-2:color(display-p3 0.957 0.985 0.977);--mint-3:color(display-p3 0.888 0.972 0.95);--mint-4:color(display-p3 0.819 0.951 0.916);--mint-5:color(display-p3 0.747 0.918 0.873);--mint-6:color(display-p3 0.668 0.87 0.818);--mint-7:color(display-p3 0.567 0.805 0.744);--mint-8:color(display-p3 0.42 0.724 0.649);--mint-9:color(display-p3 0.62 0.908 0.834);--mint-10:color(display-p3 0.585 0.871 0.797);--mint-11:color(display-p3 0.203 0.463 0.397);--mint-12:color(display-p3 0.136 0.259 0.236)}}}.dark,.dark-theme{--mint-1:#0e1515;--mint-2:#0f1b1b;--mint-3:#092c2b;--mint-4:#003a38;--mint-5:#004744;--mint-6:#105650;--mint-7:#1e685f;--mint-8:#277f70;--mint-9:#86ead4;--mint-10:#a8f5e5;--mint-11:#58d5ba;--mint-12:#c4f5e1}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--mint-1:color(display-p3 0.059 0.082 0.081);--mint-2:color(display-p3 0.068 0.104 0.105);--mint-3:color(display-p3 0.077 0.17 0.168);--mint-4:color(display-p3 0.068 0.224 0.22);--mint-5:color(display-p3 0.104 0.275 0.264);--mint-6:color(display-p3 0.154 0.332 0.313);--mint-7:color(display-p3 0.207 0.403 0.373);--mint-8:color(display-p3 0.258 0.49 0.441);--mint-9:color(display-p3 0.62 0.908 0.834);--mint-10:color(display-p3 0.725 0.954 0.898);--mint-11:color(display-p3 0.482 0.825 0.733);--mint-12:color(display-p3 0.807 0.955 0.887)}}}.light,.light-theme,:root{--mint-a1:#00d5aa06;--mint-a2:#00b18a0d;--mint-a3:#00d29e22;--mint-a4:#00cc9937;--mint-a5:#00c0914c;--mint-a6:#00b08663;--mint-a7:#00a17d81;--mint-a8:#009e7fb3;--mint-a9:#00d3a579;--mint-a10:#00c39982;--mint-a11:#007763fd;--mint-a12:#00312ae9}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--mint-a1:color(display-p3 0.02 0.804 0.608/0.02);--mint-a2:color(display-p3 0.02 0.647 0.467/0.044);--mint-a3:color(display-p3 0.004 0.761 0.553/0.114);--mint-a4:color(display-p3 0.004 0.741 0.545/0.181);--mint-a5:color(display-p3 0.004 0.678 0.51/0.255);--mint-a6:color(display-p3 0.004 0.616 0.463/0.334);--mint-a7:color(display-p3 0.004 0.549 0.412/0.432);--mint-a8:color(display-p3 0 0.529 0.392/0.581);--mint-a9:color(display-p3 0.004 0.765 0.569/0.381);--mint-a10:color(display-p3 0.004 0.69 0.51/0.416);--mint-a11:color(display-p3 0.203 0.463 0.397);--mint-a12:color(display-p3 0.136 0.259 0.236)}}}.dark,.dark-theme{--mint-a1:#00dede05;--mint-a2:#00f9f90b;--mint-a3:#00fff61d;--mint-a4:#00fff42c;--mint-a5:#00fff23a;--mint-a6:#0effeb4a;--mint-a7:#34fde55e;--mint-a8:#41ffdf76;--mint-a9:#92ffe7e9;--mint-a10:#aefeedf5;--mint-a11:#67ffded2;--mint-a12:#cbfee9f5}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--mint-a1:color(display-p3 0 0.992 0.992/0.017);--mint-a2:color(display-p3 0.071 0.98 0.98/0.043);--mint-a3:color(display-p3 0.176 0.996 0.996/0.11);--mint-a4:color(display-p3 0.071 0.996 0.973/0.169);--mint-a5:color(display-p3 0.243 1 0.949/0.223);--mint-a6:color(display-p3 0.369 1 0.933/0.286);--mint-a7:color(display-p3 0.459 1 0.914/0.362);--mint-a8:color(display-p3 0.49 1 0.89/0.454);--mint-a9:color(display-p3 0.678 0.996 0.914/0.904);--mint-a10:color(display-p3 0.761 1 0.941/0.95);--mint-a11:color(display-p3 0.482 0.825 0.733);--mint-a12:color(display-p3 0.807 0.955 0.887)}}}.light,.light-theme,:root{--lime-1:#fcfdfa;--lime-2:#f8faf3;--lime-3:#eef6d6;--lime-4:#e2f0bd;--lime-5:#d3e7a6;--lime-6:#c2da91;--lime-7:#abc978;--lime-8:#8db654;--lime-9:#bdee63;--lime-10:#b0e64c;--lime-11:#5c7c2f;--lime-12:#37401c}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--lime-1:color(display-p3 0.989 0.992 0.981);--lime-2:color(display-p3 0.975 0.98 0.954);--lime-3:color(display-p3 0.939 0.965 0.851);--lime-4:color(display-p3 0.896 0.94 0.76);--lime-5:color(display-p3 0.843 0.903 0.678);--lime-6:color(display-p3 0.778 0.852 0.599);--lime-7:color(display-p3 0.694 0.784 0.508);--lime-8:color(display-p3 0.585 0.707 0.378);--lime-9:color(display-p3 0.78 0.928 0.466);--lime-10:color(display-p3 0.734 0.896 0.397);--lime-11:color(display-p3 0.386 0.482 0.227);--lime-12:color(display-p3 0.222 0.25 0.128)}}}.dark,.dark-theme{--lime-1:#11130c;--lime-2:#151a10;--lime-3:#1f2917;--lime-4:#29371d;--lime-5:#334423;--lime-6:#3d522a;--lime-7:#496231;--lime-8:#577538;--lime-9:#bdee63;--lime-10:#d4ff70;--lime-11:#bde56c;--lime-12:#e3f7ba}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--lime-1:color(display-p3 0.067 0.073 0.048);--lime-2:color(display-p3 0.086 0.1 0.067);--lime-3:color(display-p3 0.13 0.16 0.099);--lime-4:color(display-p3 0.172 0.214 0.126);--lime-5:color(display-p3 0.213 0.266 0.153);--lime-6:color(display-p3 0.257 0.321 0.182);--lime-7:color(display-p3 0.307 0.383 0.215);--lime-8:color(display-p3 0.365 0.456 0.25);--lime-9:color(display-p3 0.78 0.928 0.466);--lime-10:color(display-p3 0.865 0.995 0.519);--lime-11:color(display-p3 0.771 0.893 0.485);--lime-12:color(display-p3 0.905 0.966 0.753)}}}.light,.light-theme,:root{--lime-a1:#66990005;--lime-a2:#6b95000c;--lime-a3:#96c80029;--lime-a4:#8fc60042;--lime-a5:#81bb0059;--lime-a6:#72aa006e;--lime-a7:#61990087;--lime-a8:#559200ab;--lime-a9:#93e4009c;--lime-a10:#8fdc00b3;--lime-a11:#375f00d0;--lime-a12:#1e2900e3}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--lime-a1:color(display-p3 0.412 0.608 0.02/0.02);--lime-a2:color(display-p3 0.514 0.592 0.024/0.048);--lime-a3:color(display-p3 0.584 0.765 0.008/0.15);--lime-a4:color(display-p3 0.561 0.757 0.004/0.24);--lime-a5:color(display-p3 0.514 0.698 0.004/0.322);--lime-a6:color(display-p3 0.443 0.627 0/0.4);--lime-a7:color(display-p3 0.376 0.561 0.004/0.491);--lime-a8:color(display-p3 0.333 0.529 0/0.624);--lime-a9:color(display-p3 0.588 0.867 0/0.534);--lime-a10:color(display-p3 0.561 0.827 0/0.604);--lime-a11:color(display-p3 0.386 0.482 0.227);--lime-a12:color(display-p3 0.222 0.25 0.128)}}}.dark,.dark-theme{--lime-a1:#11bb0003;--lime-a2:#78f7000a;--lime-a3:#9bfd4c1a;--lime-a4:#a7fe5c29;--lime-a5:#affe6537;--lime-a6:#b2fe6d46;--lime-a7:#b6ff6f57;--lime-a8:#b6fd6d6c;--lime-a9:#caff69ed;--lime-a10:#d4ff70;--lime-a11:#d1fe77e4;--lime-a12:#e9febff7}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--lime-a1:color(display-p3 0.067 0.941 0/0.009);--lime-a2:color(display-p3 0.584 0.996 0.071/0.038);--lime-a3:color(display-p3 0.69 1 0.38/0.101);--lime-a4:color(display-p3 0.729 1 0.435/0.16);--lime-a5:color(display-p3 0.745 1 0.471/0.215);--lime-a6:color(display-p3 0.769 1 0.482/0.274);--lime-a7:color(display-p3 0.769 1 0.506/0.341);--lime-a8:color(display-p3 0.784 1 0.51/0.416);--lime-a9:color(display-p3 0.839 1 0.502/0.925);--lime-a10:color(display-p3 0.871 1 0.522/0.996);--lime-a11:color(display-p3 0.771 0.893 0.485);--lime-a12:color(display-p3 0.905 0.966 0.753)}}}.light,.light-theme,:root{--yellow-1:#fdfdf9;--yellow-2:#fefce9;--yellow-3:#fffab8;--yellow-4:#fff394;--yellow-5:#ffe770;--yellow-6:#f3d768;--yellow-7:#e4c767;--yellow-8:#d5ae39;--yellow-9:#ffe629;--yellow-10:#ffdc00;--yellow-11:#9e6c00;--yellow-12:#473b1f}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--yellow-1:color(display-p3 0.992 0.992 0.978);--yellow-2:color(display-p3 0.995 0.99 0.922);--yellow-3:color(display-p3 0.997 0.982 0.749);--yellow-4:color(display-p3 0.992 0.953 0.627);--yellow-5:color(display-p3 0.984 0.91 0.51);--yellow-6:color(display-p3 0.934 0.847 0.474);--yellow-7:color(display-p3 0.876 0.785 0.46);--yellow-8:color(display-p3 0.811 0.689 0.313);--yellow-9:color(display-p3 1 0.92 0.22);--yellow-10:color(display-p3 0.977 0.868 0.291);--yellow-11:color(display-p3 0.6 0.44 0);--yellow-12:color(display-p3 0.271 0.233 0.137)}}}.dark,.dark-theme{--yellow-1:#14120b;--yellow-2:#1b180f;--yellow-3:#2d2305;--yellow-4:#362b00;--yellow-5:#433500;--yellow-6:#524202;--yellow-7:#665417;--yellow-8:#836a21;--yellow-9:#ffe629;--yellow-10:#ffff57;--yellow-11:#f5e147;--yellow-12:#f6eeb4}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--yellow-1:color(display-p3 0.078 0.069 0.047);--yellow-2:color(display-p3 0.103 0.094 0.063);--yellow-3:color(display-p3 0.168 0.137 0.039);--yellow-4:color(display-p3 0.209 0.169 0);--yellow-5:color(display-p3 0.255 0.209 0);--yellow-6:color(display-p3 0.31 0.261 0.07);--yellow-7:color(display-p3 0.389 0.331 0.135);--yellow-8:color(display-p3 0.497 0.42 0.182);--yellow-9:color(display-p3 1 0.92 0.22);--yellow-10:color(display-p3 1 1 0.456);--yellow-11:color(display-p3 0.948 0.885 0.392);--yellow-12:color(display-p3 0.959 0.934 0.731)}}}.light,.light-theme,:root{--yellow-a1:#aaaa0006;--yellow-a2:#f4dd0016;--yellow-a3:#ffee0047;--yellow-a4:#ffe3016b;--yellow-a5:#ffd5008f;--yellow-a6:#ebbc0097;--yellow-a7:#d2a10098;--yellow-a8:#c99700c6;--yellow-a9:#ffe100d6;--yellow-a10:#ffdc00;--yellow-a11:#9e6c00;--yellow-a12:#2e2000e0}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--yellow-a1:color(display-p3 0.675 0.675 0.024/0.024);--yellow-a2:color(display-p3 0.953 0.855 0.008/0.079);--yellow-a3:color(display-p3 0.988 0.925 0.004/0.251);--yellow-a4:color(display-p3 0.98 0.875 0.004/0.373);--yellow-a5:color(display-p3 0.969 0.816 0.004/0.491);--yellow-a6:color(display-p3 0.875 0.71 0/0.526);--yellow-a7:color(display-p3 0.769 0.604 0/0.542);--yellow-a8:color(display-p3 0.725 0.549 0/0.687);--yellow-a9:color(display-p3 1 0.898 0/0.781);--yellow-a10:color(display-p3 0.969 0.812 0/0.71);--yellow-a11:color(display-p3 0.6 0.44 0);--yellow-a12:color(display-p3 0.271 0.233 0.137)}}}.dark,.dark-theme{--yellow-a1:#d1510004;--yellow-a2:#f9b4000b;--yellow-a3:#ffaa001e;--yellow-a4:#fdb70028;--yellow-a5:#febb0036;--yellow-a6:#fec40046;--yellow-a7:#fdcb225c;--yellow-a8:#fdca327b;--yellow-a9:#ffe629;--yellow-a10:#ffff57;--yellow-a11:#fee949f5;--yellow-a12:#fef6baf6}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--yellow-a1:color(display-p3 0.973 0.369 0/0.013);--yellow-a2:color(display-p3 0.996 0.792 0/0.038);--yellow-a3:color(display-p3 0.996 0.71 0/0.11);--yellow-a4:color(display-p3 0.996 0.741 0/0.152);--yellow-a5:color(display-p3 0.996 0.765 0/0.202);--yellow-a6:color(display-p3 0.996 0.816 0.082/0.261);--yellow-a7:color(display-p3 1 0.831 0.263/0.345);--yellow-a8:color(display-p3 1 0.831 0.314/0.463);--yellow-a9:color(display-p3 1 0.922 0.22);--yellow-a10:color(display-p3 1 1 0.455);--yellow-a11:color(display-p3 0.948 0.885 0.392);--yellow-a12:color(display-p3 0.959 0.934 0.731)}}}.light,.light-theme,:root{--amber-1:#fefdfb;--amber-2:#fefbe9;--amber-3:#fff7c2;--amber-4:#ffee9c;--amber-5:#fbe577;--amber-6:#f3d673;--amber-7:#e9c162;--amber-8:#e2a336;--amber-9:#ffc53d;--amber-10:#ffba18;--amber-11:#ab6400;--amber-12:#4f3422}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--amber-1:color(display-p3 0.995 0.992 0.985);--amber-2:color(display-p3 0.994 0.986 0.921);--amber-3:color(display-p3 0.994 0.969 0.782);--amber-4:color(display-p3 0.989 0.937 0.65);--amber-5:color(display-p3 0.97 0.902 0.527);--amber-6:color(display-p3 0.936 0.844 0.506);--amber-7:color(display-p3 0.89 0.762 0.443);--amber-8:color(display-p3 0.85 0.65 0.3);--amber-9:color(display-p3 1 0.77 0.26);--amber-10:color(display-p3 0.959 0.741 0.274);--amber-11:color(display-p3 0.64 0.4 0);--amber-12:color(display-p3 0.294 0.208 0.145)}}}.dark,.dark-theme{--amber-1:#16120c;--amber-2:#1d180f;--amber-3:#302008;--amber-4:#3f2700;--amber-5:#4d3000;--amber-6:#5c3d05;--amber-7:#714f19;--amber-8:#8f6424;--amber-9:#ffc53d;--amber-10:#ffd60a;--amber-11:#ffca16;--amber-12:#ffe7b3}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--amber-1:color(display-p3 0.082 0.07 0.05);--amber-2:color(display-p3 0.111 0.094 0.064);--amber-3:color(display-p3 0.178 0.128 0.049);--amber-4:color(display-p3 0.239 0.156 0);--amber-5:color(display-p3 0.29 0.193 0);--amber-6:color(display-p3 0.344 0.245 0.076);--amber-7:color(display-p3 0.422 0.314 0.141);--amber-8:color(display-p3 0.535 0.399 0.189);--amber-9:color(display-p3 1 0.77 0.26);--amber-10:color(display-p3 1 0.87 0.15);--amber-11:color(display-p3 1 0.8 0.29);--amber-12:color(display-p3 0.984 0.909 0.726)}}}.light,.light-theme,:root{--amber-a1:#c0800004;--amber-a2:#f4d10016;--amber-a3:#ffde003d;--amber-a4:#ffd40063;--amber-a5:#f8cf0088;--amber-a6:#eab5008c;--amber-a7:#dc9b009d;--amber-a8:#da8a00c9;--amber-a9:#ffb300c2;--amber-a10:#ffb300e7;--amber-a11:#ab6400;--amber-a12:#341500dd}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--amber-a1:color(display-p3 0.757 0.514 0.024/0.016);--amber-a2:color(display-p3 0.902 0.804 0.008/0.079);--amber-a3:color(display-p3 0.965 0.859 0.004/0.22);--amber-a4:color(display-p3 0.969 0.82 0.004/0.35);--amber-a5:color(display-p3 0.933 0.796 0.004/0.475);--amber-a6:color(display-p3 0.875 0.682 0.004/0.495);--amber-a7:color(display-p3 0.804 0.573 0/0.557);--amber-a8:color(display-p3 0.788 0.502 0/0.699);--amber-a9:color(display-p3 1 0.686 0/0.742);--amber-a10:color(display-p3 0.945 0.643 0/0.726);--amber-a11:color(display-p3 0.64 0.4 0);--amber-a12:color(display-p3 0.294 0.208 0.145)}}}.dark,.dark-theme{--amber-a1:#e63c0006;--amber-a2:#fd9b000d;--amber-a3:#fa820022;--amber-a4:#fc820032;--amber-a5:#fd8b0041;--amber-a6:#fd9b0051;--amber-a7:#ffab2567;--amber-a8:#ffae3587;--amber-a9:#ffc53d;--amber-a10:#ffd60a;--amber-a11:#ffca16;--amber-a12:#ffe7b3}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--amber-a1:color(display-p3 0.992 0.298 0/0.017);--amber-a2:color(display-p3 0.988 0.651 0/0.047);--amber-a3:color(display-p3 1 0.6 0/0.118);--amber-a4:color(display-p3 1 0.557 0/0.185);--amber-a5:color(display-p3 1 0.592 0/0.24);--amber-a6:color(display-p3 1 0.659 0.094/0.299);--amber-a7:color(display-p3 1 0.714 0.263/0.383);--amber-a8:color(display-p3 0.996 0.729 0.306/0.5);--amber-a9:color(display-p3 1 0.769 0.259);--amber-a10:color(display-p3 1 0.871 0.149);--amber-a11:color(display-p3 1 0.8 0.29);--amber-a12:color(display-p3 0.984 0.909 0.726)}}}.light,.light-theme,:root{--gold-1:#fdfdfc;--gold-2:#faf9f2;--gold-3:#f2f0e7;--gold-4:#eae6db;--gold-5:#e1dccf;--gold-6:#d8d0bf;--gold-7:#cbc0aa;--gold-8:#b9a88d;--gold-9:#978365;--gold-10:#8c7a5e;--gold-11:#71624b;--gold-12:#3b352b}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--gold-1:color(display-p3 0.992 0.992 0.989);--gold-2:color(display-p3 0.98 0.976 0.953);--gold-3:color(display-p3 0.947 0.94 0.909);--gold-4:color(display-p3 0.914 0.904 0.865);--gold-5:color(display-p3 0.88 0.865 0.816);--gold-6:color(display-p3 0.84 0.818 0.756);--gold-7:color(display-p3 0.788 0.753 0.677);--gold-8:color(display-p3 0.715 0.66 0.565);--gold-9:color(display-p3 0.579 0.517 0.41);--gold-10:color(display-p3 0.538 0.479 0.38);--gold-11:color(display-p3 0.433 0.386 0.305);--gold-12:color(display-p3 0.227 0.209 0.173)}}}.dark,.dark-theme{--gold-1:#121211;--gold-2:#1b1a17;--gold-3:#24231f;--gold-4:#2d2b26;--gold-5:#38352e;--gold-6:#444039;--gold-7:#544f46;--gold-8:#696256;--gold-9:#978365;--gold-10:#a39073;--gold-11:#cbb99f;--gold-12:#e8e2d9}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--gold-1:color(display-p3 0.071 0.071 0.067);--gold-2:color(display-p3 0.104 0.101 0.09);--gold-3:color(display-p3 0.141 0.136 0.122);--gold-4:color(display-p3 0.177 0.17 0.152);--gold-5:color(display-p3 0.217 0.207 0.185);--gold-6:color(display-p3 0.265 0.252 0.225);--gold-7:color(display-p3 0.327 0.31 0.277);--gold-8:color(display-p3 0.407 0.384 0.342);--gold-9:color(display-p3 0.579 0.517 0.41);--gold-10:color(display-p3 0.628 0.566 0.463);--gold-11:color(display-p3 0.784 0.728 0.635);--gold-12:color(display-p3 0.906 0.887 0.855)}}}.light,.light-theme,:root{--gold-a1:#55550003;--gold-a2:#9d8a000d;--gold-a3:#75600018;--gold-a4:#6b4e0024;--gold-a5:#60460030;--gold-a6:#64440040;--gold-a7:#63420055;--gold-a8:#633d0072;--gold-a9:#5332009a;--gold-a10:#492d00a1;--gold-a11:#362100b4;--gold-a12:#130c00d4}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--gold-a1:color(display-p3 0.349 0.349 0.024/0.012);--gold-a2:color(display-p3 0.592 0.514 0.024/0.048);--gold-a3:color(display-p3 0.4 0.357 0.012/0.091);--gold-a4:color(display-p3 0.357 0.298 0.008/0.134);--gold-a5:color(display-p3 0.345 0.282 0.004/0.185);--gold-a6:color(display-p3 0.341 0.263 0.004/0.244);--gold-a7:color(display-p3 0.345 0.235 0.004/0.322);--gold-a8:color(display-p3 0.345 0.22 0.004/0.436);--gold-a9:color(display-p3 0.286 0.18 0/0.589);--gold-a10:color(display-p3 0.255 0.161 0/0.62);--gold-a11:color(display-p3 0.433 0.386 0.305);--gold-a12:color(display-p3 0.227 0.209 0.173)}}}.dark,.dark-theme{--gold-a1:#91911102;--gold-a2:#f9e29d0b;--gold-a3:#f8ecbb15;--gold-a4:#ffeec41e;--gold-a5:#feecc22a;--gold-a6:#feebcb37;--gold-a7:#ffedcd48;--gold-a8:#fdeaca5f;--gold-a9:#ffdba690;--gold-a10:#fedfb09d;--gold-a11:#fee7c6c8;--gold-a12:#fef7ede7}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--gold-a1:color(display-p3 0.855 0.855 0.071/0.005);--gold-a2:color(display-p3 0.98 0.89 0.616/0.043);--gold-a3:color(display-p3 1 0.949 0.753/0.08);--gold-a4:color(display-p3 1 0.933 0.8/0.118);--gold-a5:color(display-p3 1 0.949 0.804/0.16);--gold-a6:color(display-p3 1 0.925 0.8/0.215);--gold-a7:color(display-p3 1 0.945 0.831/0.278);--gold-a8:color(display-p3 1 0.937 0.82/0.366);--gold-a9:color(display-p3 0.996 0.882 0.69/0.551);--gold-a10:color(display-p3 1 0.894 0.725/0.601);--gold-a11:color(display-p3 0.784 0.728 0.635);--gold-a12:color(display-p3 0.906 0.887 0.855)}}}.light,.light-theme,:root{--bronze-1:#fdfcfc;--bronze-2:#fdf7f5;--bronze-3:#f6edea;--bronze-4:#efe4df;--bronze-5:#e7d9d3;--bronze-6:#dfcdc5;--bronze-7:#d3bcb3;--bronze-8:#c2a499;--bronze-9:#a18072;--bronze-10:#957468;--bronze-11:#7d5e54;--bronze-12:#43302b}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--bronze-1:color(display-p3 0.991 0.988 0.988);--bronze-2:color(display-p3 0.989 0.97 0.961);--bronze-3:color(display-p3 0.958 0.932 0.919);--bronze-4:color(display-p3 0.929 0.894 0.877);--bronze-5:color(display-p3 0.898 0.853 0.832);--bronze-6:color(display-p3 0.861 0.805 0.778);--bronze-7:color(display-p3 0.812 0.739 0.706);--bronze-8:color(display-p3 0.741 0.647 0.606);--bronze-9:color(display-p3 0.611 0.507 0.455);--bronze-10:color(display-p3 0.563 0.461 0.414);--bronze-11:color(display-p3 0.471 0.373 0.336);--bronze-12:color(display-p3 0.251 0.191 0.172)}}}.dark,.dark-theme{--bronze-1:#141110;--bronze-2:#1c1917;--bronze-3:#262220;--bronze-4:#302a27;--bronze-5:#3b3330;--bronze-6:#493e3a;--bronze-7:#5a4c47;--bronze-8:#6f5f58;--bronze-9:#a18072;--bronze-10:#ae8c7e;--bronze-11:#d4b3a5;--bronze-12:#ede0d9}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--bronze-1:color(display-p3 0.076 0.067 0.063);--bronze-2:color(display-p3 0.106 0.097 0.093);--bronze-3:color(display-p3 0.147 0.132 0.125);--bronze-4:color(display-p3 0.185 0.166 0.156);--bronze-5:color(display-p3 0.227 0.202 0.19);--bronze-6:color(display-p3 0.278 0.246 0.23);--bronze-7:color(display-p3 0.343 0.302 0.281);--bronze-8:color(display-p3 0.426 0.374 0.347);--bronze-9:color(display-p3 0.611 0.507 0.455);--bronze-10:color(display-p3 0.66 0.556 0.504);--bronze-11:color(display-p3 0.81 0.707 0.655);--bronze-12:color(display-p3 0.921 0.88 0.854)}}}.light,.light-theme,:root{--bronze-a1:#55000003;--bronze-a2:#cc33000a;--bronze-a3:#92250015;--bronze-a4:#80280020;--bronze-a5:#7423002c;--bronze-a6:#7324003a;--bronze-a7:#6c1f004c;--bronze-a8:#671c0066;--bronze-a9:#551a008d;--bronze-a10:#4c150097;--bronze-a11:#3d0f00ab;--bronze-a12:#1d0600d4}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--bronze-a1:color(display-p3 0.349 0.024 0.024/0.012);--bronze-a2:color(display-p3 0.71 0.22 0.024/0.04);--bronze-a3:color(display-p3 0.482 0.2 0.008/0.083);--bronze-a4:color(display-p3 0.424 0.133 0.004/0.122);--bronze-a5:color(display-p3 0.4 0.145 0.004/0.169);--bronze-a6:color(display-p3 0.388 0.125 0.004/0.224);--bronze-a7:color(display-p3 0.365 0.11 0.004/0.295);--bronze-a8:color(display-p3 0.341 0.102 0.004/0.393);--bronze-a9:color(display-p3 0.29 0.094 0/0.546);--bronze-a10:color(display-p3 0.255 0.082 0/0.585);--bronze-a11:color(display-p3 0.471 0.373 0.336);--bronze-a12:color(display-p3 0.251 0.191 0.172)}}}.dark,.dark-theme{--bronze-a1:#d1110004;--bronze-a2:#fbbc910c;--bronze-a3:#faceb817;--bronze-a4:#facdb622;--bronze-a5:#ffd2c12d;--bronze-a6:#ffd1c03c;--bronze-a7:#fdd0c04f;--bronze-a8:#ffd6c565;--bronze-a9:#fec7b09b;--bronze-a10:#fecab5a9;--bronze-a11:#ffd7c6d1;--bronze-a12:#fff1e9ec}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--bronze-a1:color(display-p3 0.941 0.067 0/0.009);--bronze-a2:color(display-p3 0.98 0.8 0.706/0.043);--bronze-a3:color(display-p3 0.988 0.851 0.761/0.085);--bronze-a4:color(display-p3 0.996 0.839 0.78/0.127);--bronze-a5:color(display-p3 0.996 0.863 0.773/0.173);--bronze-a6:color(display-p3 1 0.863 0.796/0.227);--bronze-a7:color(display-p3 1 0.867 0.8/0.295);--bronze-a8:color(display-p3 1 0.859 0.788/0.387);--bronze-a9:color(display-p3 1 0.82 0.733/0.585);--bronze-a10:color(display-p3 1 0.839 0.761/0.635);--bronze-a11:color(display-p3 0.81 0.707 0.655);--bronze-a12:color(display-p3 0.921 0.88 0.854)}}}.light,.light-theme,:root{--gray-1:#fcfcfc;--gray-2:#f9f9f9;--gray-3:#f0f0f0;--gray-4:#e8e8e8;--gray-5:#e0e0e0;--gray-6:#d9d9d9;--gray-7:#cecece;--gray-8:#bbb;--gray-9:#8d8d8d;--gray-10:#838383;--gray-11:#646464;--gray-12:#202020}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--gray-1:color(display-p3 0.988 0.988 0.988);--gray-2:color(display-p3 0.975 0.975 0.975);--gray-3:color(display-p3 0.939 0.939 0.939);--gray-4:color(display-p3 0.908 0.908 0.908);--gray-5:color(display-p3 0.88 0.88 0.88);--gray-6:color(display-p3 0.849 0.849 0.849);--gray-7:color(display-p3 0.807 0.807 0.807);--gray-8:color(display-p3 0.732 0.732 0.732);--gray-9:color(display-p3 0.553 0.553 0.553);--gray-10:color(display-p3 0.512 0.512 0.512);--gray-11:color(display-p3 0.392 0.392 0.392);--gray-12:color(display-p3 0.125 0.125 0.125)}}}.dark,.dark-theme{--gray-1:#111;--gray-2:#191919;--gray-3:#222;--gray-4:#2a2a2a;--gray-5:#313131;--gray-6:#3a3a3a;--gray-7:#484848;--gray-8:#606060;--gray-9:#6e6e6e;--gray-10:#7b7b7b;--gray-11:#b4b4b4;--gray-12:#eee}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--gray-1:color(display-p3 0.067 0.067 0.067);--gray-2:color(display-p3 0.098 0.098 0.098);--gray-3:color(display-p3 0.135 0.135 0.135);--gray-4:color(display-p3 0.163 0.163 0.163);--gray-5:color(display-p3 0.192 0.192 0.192);--gray-6:color(display-p3 0.228 0.228 0.228);--gray-7:color(display-p3 0.283 0.283 0.283);--gray-8:color(display-p3 0.375 0.375 0.375);--gray-9:color(display-p3 0.431 0.431 0.431);--gray-10:color(display-p3 0.484 0.484 0.484);--gray-11:color(display-p3 0.706 0.706 0.706);--gray-12:color(display-p3 0.933 0.933 0.933)}}}.light,.light-theme,:root{--gray-a1:#00000003;--gray-a2:#00000006;--gray-a3:#0000000f;--gray-a4:#00000017;--gray-a5:#0000001f;--gray-a6:#00000026;--gray-a7:#00000031;--gray-a8:#00000044;--gray-a9:#00000072;--gray-a10:#0000007c;--gray-a11:#0000009b;--gray-a12:#000000df}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--gray-a1:color(display-p3 0 0 0/0.012);--gray-a2:color(display-p3 0 0 0/0.024);--gray-a3:color(display-p3 0 0 0/0.063);--gray-a4:color(display-p3 0 0 0/0.09);--gray-a5:color(display-p3 0 0 0/0.122);--gray-a6:color(display-p3 0 0 0/0.153);--gray-a7:color(display-p3 0 0 0/0.192);--gray-a8:color(display-p3 0 0 0/0.267);--gray-a9:color(display-p3 0 0 0/0.447);--gray-a10:color(display-p3 0 0 0/0.486);--gray-a11:color(display-p3 0 0 0/0.608);--gray-a12:color(display-p3 0 0 0/0.875)}}}.dark,.dark-theme{--gray-a1:#00000000;--gray-a2:#ffffff09;--gray-a3:#ffffff12;--gray-a4:#ffffff1b;--gray-a5:#ffffff22;--gray-a6:#ffffff2c;--gray-a7:#ffffff3b;--gray-a8:#ffffff55;--gray-a9:#ffffff64;--gray-a10:#ffffff72;--gray-a11:#ffffffaf;--gray-a12:#ffffffed}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--gray-a1:color(display-p3 0 0 0/0);--gray-a2:color(display-p3 1 1 1/0.034);--gray-a3:color(display-p3 1 1 1/0.071);--gray-a4:color(display-p3 1 1 1/0.105);--gray-a5:color(display-p3 1 1 1/0.134);--gray-a6:color(display-p3 1 1 1/0.172);--gray-a7:color(display-p3 1 1 1/0.231);--gray-a8:color(display-p3 1 1 1/0.332);--gray-a9:color(display-p3 1 1 1/0.391);--gray-a10:color(display-p3 1 1 1/0.445);--gray-a11:color(display-p3 1 1 1/0.685);--gray-a12:color(display-p3 1 1 1/0.929)}}}.light,.light-theme,:root{--mauve-1:#fdfcfd;--mauve-2:#faf9fb;--mauve-3:#f2eff3;--mauve-4:#eae7ec;--mauve-5:#e3dfe6;--mauve-6:#dbd8e0;--mauve-7:#d0cdd7;--mauve-8:#bcbac7;--mauve-9:#8e8c99;--mauve-10:#84828e;--mauve-11:#65636d;--mauve-12:#211f26}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--mauve-1:color(display-p3 0.991 0.988 0.992);--mauve-2:color(display-p3 0.98 0.976 0.984);--mauve-3:color(display-p3 0.946 0.938 0.952);--mauve-4:color(display-p3 0.915 0.906 0.925);--mauve-5:color(display-p3 0.886 0.876 0.901);--mauve-6:color(display-p3 0.856 0.846 0.875);--mauve-7:color(display-p3 0.814 0.804 0.84);--mauve-8:color(display-p3 0.735 0.728 0.777);--mauve-9:color(display-p3 0.555 0.549 0.596);--mauve-10:color(display-p3 0.514 0.508 0.552);--mauve-11:color(display-p3 0.395 0.388 0.424);--mauve-12:color(display-p3 0.128 0.122 0.147)}}}.dark,.dark-theme{--mauve-1:#121113;--mauve-2:#1a191b;--mauve-3:#232225;--mauve-4:#2b292d;--mauve-5:#323035;--mauve-6:#3c393f;--mauve-7:#49474e;--mauve-8:#625f69;--mauve-9:#6f6d78;--mauve-10:#7c7a85;--mauve-11:#b5b2bc;--mauve-12:#eeeef0}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--mauve-1:color(display-p3 0.07 0.067 0.074);--mauve-2:color(display-p3 0.101 0.098 0.105);--mauve-3:color(display-p3 0.138 0.134 0.144);--mauve-4:color(display-p3 0.167 0.161 0.175);--mauve-5:color(display-p3 0.196 0.189 0.206);--mauve-6:color(display-p3 0.232 0.225 0.245);--mauve-7:color(display-p3 0.286 0.277 0.302);--mauve-8:color(display-p3 0.383 0.373 0.408);--mauve-9:color(display-p3 0.434 0.428 0.467);--mauve-10:color(display-p3 0.487 0.48 0.519);--mauve-11:color(display-p3 0.707 0.7 0.735);--mauve-12:color(display-p3 0.933 0.933 0.94)}}}.light,.light-theme,:root{--mauve-a1:#55005503;--mauve-a2:#2b005506;--mauve-a3:#30004010;--mauve-a4:#20003618;--mauve-a5:#20003820;--mauve-a6:#14003527;--mauve-a7:#10003332;--mauve-a8:#08003145;--mauve-a9:#05001d73;--mauve-a10:#0500197d;--mauve-a11:#0400119c;--mauve-a12:#020008e0}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--mauve-a1:color(display-p3 0.349 0.024 0.349/0.012);--mauve-a2:color(display-p3 0.184 0.024 0.349/0.024);--mauve-a3:color(display-p3 0.129 0.008 0.255/0.063);--mauve-a4:color(display-p3 0.094 0.012 0.216/0.095);--mauve-a5:color(display-p3 0.098 0.008 0.224/0.126);--mauve-a6:color(display-p3 0.055 0.004 0.18/0.153);--mauve-a7:color(display-p3 0.067 0.008 0.184/0.197);--mauve-a8:color(display-p3 0.02 0.004 0.176/0.271);--mauve-a9:color(display-p3 0.02 0.004 0.106/0.451);--mauve-a10:color(display-p3 0.012 0.004 0.09/0.491);--mauve-a11:color(display-p3 0.016 0 0.059/0.612);--mauve-a12:color(display-p3 0.008 0 0.027/0.879)}}}.dark,.dark-theme{--mauve-a1:#00000000;--mauve-a2:#f5f4f609;--mauve-a3:#ebeaf814;--mauve-a4:#eee5f81d;--mauve-a5:#efe6fe25;--mauve-a6:#f1e6fd30;--mauve-a7:#eee9ff40;--mauve-a8:#eee7ff5d;--mauve-a9:#eae6fd6e;--mauve-a10:#ece9fd7c;--mauve-a11:#f5f1ffb7;--mauve-a12:#fdfdffef}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--mauve-a1:color(display-p3 0 0 0/0);--mauve-a2:color(display-p3 0.996 0.992 1/0.034);--mauve-a3:color(display-p3 0.937 0.933 0.992/0.077);--mauve-a4:color(display-p3 0.957 0.918 0.996/0.111);--mauve-a5:color(display-p3 0.937 0.906 0.996/0.145);--mauve-a6:color(display-p3 0.953 0.925 0.996/0.183);--mauve-a7:color(display-p3 0.945 0.929 1/0.246);--mauve-a8:color(display-p3 0.937 0.918 1/0.361);--mauve-a9:color(display-p3 0.933 0.918 1/0.424);--mauve-a10:color(display-p3 0.941 0.925 1/0.479);--mauve-a11:color(display-p3 0.965 0.961 1/0.712);--mauve-a12:color(display-p3 0.992 0.992 1/0.937)}}}.light,.light-theme,:root{--slate-1:#fcfcfd;--slate-2:#f9f9fb;--slate-3:#f0f0f3;--slate-4:#e8e8ec;--slate-5:#e0e1e6;--slate-6:#d9d9e0;--slate-7:#cdced6;--slate-8:#b9bbc6;--slate-9:#8b8d98;--slate-10:#80838d;--slate-11:#60646c;--slate-12:#1c2024}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--slate-1:color(display-p3 0.988 0.988 0.992);--slate-2:color(display-p3 0.976 0.976 0.984);--slate-3:color(display-p3 0.94 0.941 0.953);--slate-4:color(display-p3 0.908 0.909 0.925);--slate-5:color(display-p3 0.88 0.881 0.901);--slate-6:color(display-p3 0.85 0.852 0.876);--slate-7:color(display-p3 0.805 0.808 0.838);--slate-8:color(display-p3 0.727 0.733 0.773);--slate-9:color(display-p3 0.547 0.553 0.592);--slate-10:color(display-p3 0.503 0.512 0.549);--slate-11:color(display-p3 0.379 0.392 0.421);--slate-12:color(display-p3 0.113 0.125 0.14)}}}.dark,.dark-theme{--slate-1:#111113;--slate-2:#18191b;--slate-3:#212225;--slate-4:#272a2d;--slate-5:#2e3135;--slate-6:#363a3f;--slate-7:#43484e;--slate-8:#5a6169;--slate-9:#696e77;--slate-10:#777b84;--slate-11:#b0b4ba;--slate-12:#edeef0}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--slate-1:color(display-p3 0.067 0.067 0.074);--slate-2:color(display-p3 0.095 0.098 0.105);--slate-3:color(display-p3 0.13 0.135 0.145);--slate-4:color(display-p3 0.156 0.163 0.176);--slate-5:color(display-p3 0.183 0.191 0.206);--slate-6:color(display-p3 0.215 0.226 0.244);--slate-7:color(display-p3 0.265 0.28 0.302);--slate-8:color(display-p3 0.357 0.381 0.409);--slate-9:color(display-p3 0.415 0.431 0.463);--slate-10:color(display-p3 0.469 0.483 0.514);--slate-11:color(display-p3 0.692 0.704 0.728);--slate-12:color(display-p3 0.93 0.933 0.94)}}}.light,.light-theme,:root{--slate-a1:#00005503;--slate-a2:#00005506;--slate-a3:#0000330f;--slate-a4:#00002d17;--slate-a5:#0009321f;--slate-a6:#00002f26;--slate-a7:#00062e32;--slate-a8:#00083046;--slate-a9:#00051d74;--slate-a10:#00071b7f;--slate-a11:#0007149f;--slate-a12:#000509e3}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--slate-a1:color(display-p3 0.024 0.024 0.349/0.012);--slate-a2:color(display-p3 0.024 0.024 0.349/0.024);--slate-a3:color(display-p3 0.004 0.004 0.204/0.059);--slate-a4:color(display-p3 0.012 0.012 0.184/0.091);--slate-a5:color(display-p3 0.004 0.039 0.2/0.122);--slate-a6:color(display-p3 0.008 0.008 0.165/0.15);--slate-a7:color(display-p3 0.008 0.027 0.184/0.197);--slate-a8:color(display-p3 0.004 0.031 0.176/0.275);--slate-a9:color(display-p3 0.004 0.02 0.106/0.455);--slate-a10:color(display-p3 0.004 0.027 0.098/0.499);--slate-a11:color(display-p3 0 0.02 0.063/0.62);--slate-a12:color(display-p3 0 0.012 0.031/0.887)}}}.dark,.dark-theme{--slate-a1:#00000000;--slate-a2:#d8f4f609;--slate-a3:#ddeaf814;--slate-a4:#d3edf81d;--slate-a5:#d9edfe25;--slate-a6:#d6ebfd30;--slate-a7:#d9edff40;--slate-a8:#d9edff5d;--slate-a9:#dfebfd6d;--slate-a10:#e5edfd7b;--slate-a11:#f1f7feb5;--slate-a12:#fcfdffef}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--slate-a1:color(display-p3 0 0 0/0);--slate-a2:color(display-p3 0.875 0.992 1/0.034);--slate-a3:color(display-p3 0.882 0.933 0.992/0.077);--slate-a4:color(display-p3 0.882 0.953 0.996/0.111);--slate-a5:color(display-p3 0.878 0.929 0.996/0.145);--slate-a6:color(display-p3 0.882 0.949 0.996/0.183);--slate-a7:color(display-p3 0.882 0.929 1/0.246);--slate-a8:color(display-p3 0.871 0.937 1/0.361);--slate-a9:color(display-p3 0.898 0.937 1/0.42);--slate-a10:color(display-p3 0.918 0.945 1/0.475);--slate-a11:color(display-p3 0.949 0.969 0.996/0.708);--slate-a12:color(display-p3 0.988 0.992 1/0.937)}}}.light,.light-theme,:root{--sage-1:#fbfdfc;--sage-2:#f7f9f8;--sage-3:#eef1f0;--sage-4:#e6e9e8;--sage-5:#dfe2e0;--sage-6:#d7dad9;--sage-7:#cbcfcd;--sage-8:#b8bcba;--sage-9:#868e8b;--sage-10:#7c8481;--sage-11:#5f6563;--sage-12:#1a211e}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--sage-1:color(display-p3 0.986 0.992 0.988);--sage-2:color(display-p3 0.97 0.977 0.974);--sage-3:color(display-p3 0.935 0.944 0.94);--sage-4:color(display-p3 0.904 0.913 0.909);--sage-5:color(display-p3 0.875 0.885 0.88);--sage-6:color(display-p3 0.844 0.854 0.849);--sage-7:color(display-p3 0.8 0.811 0.806);--sage-8:color(display-p3 0.725 0.738 0.732);--sage-9:color(display-p3 0.531 0.556 0.546);--sage-10:color(display-p3 0.492 0.515 0.506);--sage-11:color(display-p3 0.377 0.395 0.389);--sage-12:color(display-p3 0.107 0.129 0.118)}}}.dark,.dark-theme{--sage-1:#101211;--sage-2:#171918;--sage-3:#202221;--sage-4:#272a29;--sage-5:#2e3130;--sage-6:#373b39;--sage-7:#444947;--sage-8:#5b625f;--sage-9:#63706b;--sage-10:#717d79;--sage-11:#adb5b2;--sage-12:#eceeed}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--sage-1:color(display-p3 0.064 0.07 0.067);--sage-2:color(display-p3 0.092 0.098 0.094);--sage-3:color(display-p3 0.128 0.135 0.131);--sage-4:color(display-p3 0.155 0.164 0.159);--sage-5:color(display-p3 0.183 0.193 0.188);--sage-6:color(display-p3 0.218 0.23 0.224);--sage-7:color(display-p3 0.269 0.285 0.277);--sage-8:color(display-p3 0.362 0.382 0.373);--sage-9:color(display-p3 0.398 0.438 0.421);--sage-10:color(display-p3 0.453 0.49 0.474);--sage-11:color(display-p3 0.685 0.709 0.697);--sage-12:color(display-p3 0.927 0.933 0.93)}}}.light,.light-theme,:root{--sage-a1:#00804004;--sage-a2:#00402008;--sage-a3:#002d1e11;--sage-a4:#001f1519;--sage-a5:#00180820;--sage-a6:#00140d28;--sage-a7:#00140a34;--sage-a8:#000f0847;--sage-a9:#00110b79;--sage-a10:#00100a83;--sage-a11:#000a07a0;--sage-a12:#000805e5}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--sage-a1:color(display-p3 0.024 0.514 0.267/0.016);--sage-a2:color(display-p3 0.02 0.267 0.145/0.032);--sage-a3:color(display-p3 0.008 0.184 0.125/0.067);--sage-a4:color(display-p3 0.012 0.094 0.051/0.095);--sage-a5:color(display-p3 0.008 0.098 0.035/0.126);--sage-a6:color(display-p3 0.004 0.078 0.027/0.157);--sage-a7:color(display-p3 0 0.059 0.039/0.2);--sage-a8:color(display-p3 0.004 0.047 0.031/0.275);--sage-a9:color(display-p3 0.004 0.059 0.035/0.471);--sage-a10:color(display-p3 0 0.047 0.031/0.51);--sage-a11:color(display-p3 0 0.031 0.02/0.624);--sage-a12:color(display-p3 0 0.027 0.012/0.895)}}}.dark,.dark-theme{--sage-a1:#00000000;--sage-a2:#f0f2f108;--sage-a3:#f3f5f412;--sage-a4:#f2fefd1a;--sage-a5:#f1fbfa22;--sage-a6:#edfbf42d;--sage-a7:#edfcf73c;--sage-a8:#ebfdf657;--sage-a9:#dffdf266;--sage-a10:#e5fdf674;--sage-a11:#f4fefbb0;--sage-a12:#fdfffeed}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--sage-a1:color(display-p3 0 0 0/0);--sage-a2:color(display-p3 0.976 0.988 0.984/0.03);--sage-a3:color(display-p3 0.992 0.945 0.941/0.072);--sage-a4:color(display-p3 0.988 0.996 0.992/0.102);--sage-a5:color(display-p3 0.992 1 0.996/0.131);--sage-a6:color(display-p3 0.973 1 0.976/0.173);--sage-a7:color(display-p3 0.957 1 0.976/0.233);--sage-a8:color(display-p3 0.957 1 0.984/0.334);--sage-a9:color(display-p3 0.902 1 0.957/0.397);--sage-a10:color(display-p3 0.929 1 0.973/0.452);--sage-a11:color(display-p3 0.969 1 0.988/0.688);--sage-a12:color(display-p3 0.992 1 0.996/0.929)}}}.light,.light-theme,:root{--olive-1:#fcfdfc;--olive-2:#f8faf8;--olive-3:#eff1ef;--olive-4:#e7e9e7;--olive-5:#dfe2df;--olive-6:#d7dad7;--olive-7:#cccfcc;--olive-8:#b9bcb8;--olive-9:#898e87;--olive-10:#7f847d;--olive-11:#60655f;--olive-12:#1d211c}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--olive-1:color(display-p3 0.989 0.992 0.989);--olive-2:color(display-p3 0.974 0.98 0.973);--olive-3:color(display-p3 0.939 0.945 0.937);--olive-4:color(display-p3 0.907 0.914 0.905);--olive-5:color(display-p3 0.878 0.885 0.875);--olive-6:color(display-p3 0.846 0.855 0.843);--olive-7:color(display-p3 0.803 0.812 0.8);--olive-8:color(display-p3 0.727 0.738 0.723);--olive-9:color(display-p3 0.541 0.556 0.532);--olive-10:color(display-p3 0.5 0.515 0.491);--olive-11:color(display-p3 0.38 0.395 0.374);--olive-12:color(display-p3 0.117 0.129 0.111)}}}.dark,.dark-theme{--olive-1:#111210;--olive-2:#181917;--olive-3:#212220;--olive-4:#282a27;--olive-5:#2f312e;--olive-6:#383a36;--olive-7:#454843;--olive-8:#5c625b;--olive-9:#687066;--olive-10:#767d74;--olive-11:#afb5ad;--olive-12:#eceeec}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--olive-1:color(display-p3 0.067 0.07 0.063);--olive-2:color(display-p3 0.095 0.098 0.091);--olive-3:color(display-p3 0.131 0.135 0.126);--olive-4:color(display-p3 0.158 0.163 0.153);--olive-5:color(display-p3 0.186 0.192 0.18);--olive-6:color(display-p3 0.221 0.229 0.215);--olive-7:color(display-p3 0.273 0.284 0.266);--olive-8:color(display-p3 0.365 0.382 0.359);--olive-9:color(display-p3 0.414 0.438 0.404);--olive-10:color(display-p3 0.467 0.49 0.458);--olive-11:color(display-p3 0.69 0.709 0.682);--olive-12:color(display-p3 0.927 0.933 0.926)}}}.light,.light-theme,:root{--olive-a1:#00550003;--olive-a2:#00490007;--olive-a3:#00200010;--olive-a4:#00160018;--olive-a5:#00180020;--olive-a6:#00140028;--olive-a7:#000f0033;--olive-a8:#040f0047;--olive-a9:#050f0078;--olive-a10:#040e0082;--olive-a11:#020a00a0;--olive-a12:#010600e3}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--olive-a1:color(display-p3 0.024 0.349 0.024/0.012);--olive-a2:color(display-p3 0.024 0.302 0.024/0.028);--olive-a3:color(display-p3 0.008 0.129 0.008/0.063);--olive-a4:color(display-p3 0.012 0.094 0.012/0.095);--olive-a5:color(display-p3 0.035 0.098 0.008/0.126);--olive-a6:color(display-p3 0.027 0.078 0.004/0.157);--olive-a7:color(display-p3 0.02 0.059 0/0.2);--olive-a8:color(display-p3 0.02 0.059 0.004/0.279);--olive-a9:color(display-p3 0.02 0.051 0.004/0.467);--olive-a10:color(display-p3 0.024 0.047 0/0.51);--olive-a11:color(display-p3 0.012 0.039 0/0.628);--olive-a12:color(display-p3 0.008 0.024 0/0.891)}}}.dark,.dark-theme{--olive-a1:#00000000;--olive-a2:#f1f2f008;--olive-a3:#f4f5f312;--olive-a4:#f3fef21a;--olive-a5:#f2fbf122;--olive-a6:#f4faed2c;--olive-a7:#f2fced3b;--olive-a8:#edfdeb57;--olive-a9:#ebfde766;--olive-a10:#f0fdec74;--olive-a11:#f6fef4b0;--olive-a12:#fdfffded}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--olive-a1:color(display-p3 0 0 0/0);--olive-a2:color(display-p3 0.984 0.988 0.976/0.03);--olive-a3:color(display-p3 0.992 0.996 0.988/0.068);--olive-a4:color(display-p3 0.953 0.996 0.949/0.102);--olive-a5:color(display-p3 0.969 1 0.965/0.131);--olive-a6:color(display-p3 0.973 1 0.969/0.169);--olive-a7:color(display-p3 0.98 1 0.961/0.228);--olive-a8:color(display-p3 0.961 1 0.957/0.334);--olive-a9:color(display-p3 0.949 1 0.922/0.397);--olive-a10:color(display-p3 0.953 1 0.941/0.452);--olive-a11:color(display-p3 0.976 1 0.965/0.688);--olive-a12:color(display-p3 0.992 1 0.992/0.929)}}}.light,.light-theme,:root{--sand-1:#fdfdfc;--sand-2:#f9f9f8;--sand-3:#f1f0ef;--sand-4:#e9e8e6;--sand-5:#e2e1de;--sand-6:#dad9d6;--sand-7:#cfceca;--sand-8:#bcbbb5;--sand-9:#8d8d86;--sand-10:#82827c;--sand-11:#63635e;--sand-12:#21201c}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--sand-1:color(display-p3 0.992 0.992 0.989);--sand-2:color(display-p3 0.977 0.977 0.973);--sand-3:color(display-p3 0.943 0.942 0.936);--sand-4:color(display-p3 0.913 0.912 0.903);--sand-5:color(display-p3 0.885 0.883 0.873);--sand-6:color(display-p3 0.854 0.852 0.839);--sand-7:color(display-p3 0.813 0.81 0.794);--sand-8:color(display-p3 0.738 0.734 0.713);--sand-9:color(display-p3 0.553 0.553 0.528);--sand-10:color(display-p3 0.511 0.511 0.488);--sand-11:color(display-p3 0.388 0.388 0.37);--sand-12:color(display-p3 0.129 0.126 0.111)}}}.dark,.dark-theme{--sand-1:#111110;--sand-2:#191918;--sand-3:#222221;--sand-4:#2a2a28;--sand-5:#31312e;--sand-6:#3b3a37;--sand-7:#494844;--sand-8:#62605b;--sand-9:#6f6d66;--sand-10:#7c7b74;--sand-11:#b5b3ad;--sand-12:#eeeeec}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--sand-1:color(display-p3 0.067 0.067 0.063);--sand-2:color(display-p3 0.098 0.098 0.094);--sand-3:color(display-p3 0.135 0.135 0.129);--sand-4:color(display-p3 0.164 0.163 0.156);--sand-5:color(display-p3 0.193 0.192 0.183);--sand-6:color(display-p3 0.23 0.229 0.217);--sand-7:color(display-p3 0.285 0.282 0.267);--sand-8:color(display-p3 0.384 0.378 0.357);--sand-9:color(display-p3 0.434 0.428 0.403);--sand-10:color(display-p3 0.487 0.481 0.456);--sand-11:color(display-p3 0.707 0.703 0.68);--sand-12:color(display-p3 0.933 0.933 0.926)}}}.light,.light-theme,:root{--sand-a1:#55550003;--sand-a2:#25250007;--sand-a3:#20100010;--sand-a4:#1f150019;--sand-a5:#1f180021;--sand-a6:#19130029;--sand-a7:#19140035;--sand-a8:#1915014a;--sand-a9:#0f0f0079;--sand-a10:#0c0c0083;--sand-a11:#080800a1;--sand-a12:#060500e3}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.light,.light-theme,:root{--sand-a1:color(display-p3 0.349 0.349 0.024/0.012);--sand-a2:color(display-p3 0.161 0.161 0.024/0.028);--sand-a3:color(display-p3 0.067 0.067 0.008/0.063);--sand-a4:color(display-p3 0.129 0.129 0.012/0.099);--sand-a5:color(display-p3 0.098 0.067 0.008/0.126);--sand-a6:color(display-p3 0.102 0.075 0.004/0.161);--sand-a7:color(display-p3 0.098 0.098 0.004/0.208);--sand-a8:color(display-p3 0.086 0.075 0.004/0.287);--sand-a9:color(display-p3 0.051 0.051 0.004/0.471);--sand-a10:color(display-p3 0.047 0.047 0/0.514);--sand-a11:color(display-p3 0.031 0.031 0/0.632);--sand-a12:color(display-p3 0.024 0.02 0/0.891)}}}.dark,.dark-theme{--sand-a1:#00000000;--sand-a2:#f4f4f309;--sand-a3:#f6f6f513;--sand-a4:#fefef31b;--sand-a5:#fbfbeb23;--sand-a6:#fffaed2d;--sand-a7:#fffbed3c;--sand-a8:#fff9eb57;--sand-a9:#fffae965;--sand-a10:#fffdee73;--sand-a11:#fffcf4b0;--sand-a12:#fffffded}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){.dark,.dark-theme{--sand-a1:color(display-p3 0 0 0/0);--sand-a2:color(display-p3 0.992 0.992 0.988/0.034);--sand-a3:color(display-p3 0.996 0.996 0.992/0.072);--sand-a4:color(display-p3 0.992 0.992 0.953/0.106);--sand-a5:color(display-p3 1 1 0.965/0.135);--sand-a6:color(display-p3 1 0.976 0.929/0.177);--sand-a7:color(display-p3 1 0.984 0.929/0.236);--sand-a8:color(display-p3 1 0.976 0.925/0.341);--sand-a9:color(display-p3 1 0.98 0.925/0.395);--sand-a10:color(display-p3 1 0.992 0.933/0.45);--sand-a11:color(display-p3 1 0.996 0.961/0.685);--sand-a12:color(display-p3 1 1 0.992/0.929)}}}:root{--black-a1:rgba(0,0,0,.05);--black-a2:rgba(0,0,0,.1);--black-a3:rgba(0,0,0,.15);--black-a4:rgba(0,0,0,.2);--black-a5:rgba(0,0,0,.3);--black-a6:rgba(0,0,0,.4);--black-a7:rgba(0,0,0,.5);--black-a8:rgba(0,0,0,.6);--black-a9:rgba(0,0,0,.7);--black-a10:rgba(0,0,0,.8);--black-a11:rgba(0,0,0,.9);--black-a12:rgba(0,0,0,.95)}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){:root{--black-a1:color(display-p3 0 0 0/0.05);--black-a2:color(display-p3 0 0 0/0.1);--black-a3:color(display-p3 0 0 0/0.15);--black-a4:color(display-p3 0 0 0/0.2);--black-a5:color(display-p3 0 0 0/0.3);--black-a6:color(display-p3 0 0 0/0.4);--black-a7:color(display-p3 0 0 0/0.5);--black-a8:color(display-p3 0 0 0/0.6);--black-a9:color(display-p3 0 0 0/0.7);--black-a10:color(display-p3 0 0 0/0.8);--black-a11:color(display-p3 0 0 0/0.9);--black-a12:color(display-p3 0 0 0/0.95)}}}:root{--white-a1:hsla(0,0%,100%,.05);--white-a2:hsla(0,0%,100%,.1);--white-a3:hsla(0,0%,100%,.15);--white-a4:hsla(0,0%,100%,.2);--white-a5:hsla(0,0%,100%,.3);--white-a6:hsla(0,0%,100%,.4);--white-a7:hsla(0,0%,100%,.5);--white-a8:hsla(0,0%,100%,.6);--white-a9:hsla(0,0%,100%,.7);--white-a10:hsla(0,0%,100%,.8);--white-a11:hsla(0,0%,100%,.9);--white-a12:hsla(0,0%,100%,.95)}@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){:root{--white-a1:color(display-p3 1 1 1/0.05);--white-a2:color(display-p3 1 1 1/0.1);--white-a3:color(display-p3 1 1 1/0.15);--white-a4:color(display-p3 1 1 1/0.2);--white-a5:color(display-p3 1 1 1/0.3);--white-a6:color(display-p3 1 1 1/0.4);--white-a7:color(display-p3 1 1 1/0.5);--white-a8:color(display-p3 1 1 1/0.6);--white-a9:color(display-p3 1 1 1/0.7);--white-a10:color(display-p3 1 1 1/0.8);--white-a11:color(display-p3 1 1 1/0.9);--white-a12:color(display-p3 1 1 1/0.95)}}}:root{--tomato-9-contrast:#fff;--red-9-contrast:#fff;--ruby-9-contrast:#fff;--crimson-9-contrast:#fff;--pink-9-contrast:#fff;--plum-9-contrast:#fff;--purple-9-contrast:#fff;--violet-9-contrast:#fff;--iris-9-contrast:#fff;--indigo-9-contrast:#fff;--blue-9-contrast:#fff;--cyan-9-contrast:#fff;--teal-9-contrast:#fff;--jade-9-contrast:#fff;--green-9-contrast:#fff;--grass-9-contrast:#fff;--orange-9-contrast:#fff;--brown-9-contrast:#fff;--sky-9-contrast:#1c2024;--mint-9-contrast:#1a211e;--lime-9-contrast:#1d211c;--yellow-9-contrast:#21201c;--amber-9-contrast:#21201c;--gold-9-contrast:#fff;--bronze-9-contrast:#fff;--gray-9-contrast:#fff}.light,.light-theme,:root{--gray-surface:#ffffffcc;--mauve-surface:#ffffffcc;--slate-surface:#ffffffcc;--sage-surface:#ffffffcc;--olive-surface:#ffffffcc;--sand-surface:#ffffffcc;--tomato-surface:#fff6f5cc;--red-surface:#fff5f5cc;--ruby-surface:#fff5f6cc;--crimson-surface:#fef5f8cc;--pink-surface:#fef5facc;--plum-surface:#fdf5fdcc;--purple-surface:#faf5fecc;--violet-surface:#f9f6ffcc;--iris-surface:#f6f6ffcc;--indigo-surface:#f5f8ffcc;--blue-surface:#f1f9ffcc;--cyan-surface:#eff9facc;--teal-surface:#f0faf8cc;--jade-surface:#f1faf5cc;--green-surface:#f1faf4cc;--grass-surface:#f3faf3cc;--brown-surface:#fbf8f4cc;--bronze-surface:#fdf5f3cc;--gold-surface:#f9f8efcc;--sky-surface:#eef9fdcc;--mint-surface:#effaf8cc;--lime-surface:#f6f9f0cc;--yellow-surface:#fefbe4cc;--amber-surface:#fefae4cc;--orange-surface:#fff5e9cc;@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){&{--gray-surface:color(display-p3 1 1 1/0.8);--mauve-surface:color(display-p3 1 1 1/0.8);--slate-surface:color(display-p3 1 1 1/0.8);--sage-surface:color(display-p3 1 1 1/0.8);--olive-surface:color(display-p3 1 1 1/0.8);--sand-surface:color(display-p3 1 1 1/0.8);--tomato-surface:color(display-p3 0.9922 0.9647 0.9608/0.8);--red-surface:color(display-p3 0.9961 0.9647 0.9647/0.8);--ruby-surface:color(display-p3 0.9961 0.9647 0.9647/0.8);--crimson-surface:color(display-p3 0.9922 0.9608 0.9725/0.8);--pink-surface:color(display-p3 0.9922 0.9608 0.9804/0.8);--plum-surface:color(display-p3 0.9843 0.9647 0.9843/0.8);--purple-surface:color(display-p3 0.9804 0.9647 0.9922/0.8);--violet-surface:color(display-p3 0.9725 0.9647 0.9961/0.8);--iris-surface:color(display-p3 0.9647 0.9647 0.9961/0.8);--indigo-surface:color(display-p3 0.9647 0.9725 0.9961/0.8);--blue-surface:color(display-p3 0.9529 0.9765 0.9961/0.8);--cyan-surface:color(display-p3 0.9412 0.9765 0.9804/0.8);--teal-surface:color(display-p3 0.9451 0.9804 0.9725/0.8);--jade-surface:color(display-p3 0.9529 0.9804 0.9608/0.8);--green-surface:color(display-p3 0.9569 0.9804 0.9608/0.8);--grass-surface:color(display-p3 0.9569 0.9804 0.9569/0.8);--brown-surface:color(display-p3 0.9843 0.9725 0.9569/0.8);--bronze-surface:color(display-p3 0.9843 0.9608 0.9529/0.8);--gold-surface:color(display-p3 0.9765 0.9725 0.9412/0.8);--sky-surface:color(display-p3 0.9412 0.9765 0.9843/0.8);--mint-surface:color(display-p3 0.9451 0.9804 0.9725/0.8);--lime-surface:color(display-p3 0.9725 0.9765 0.9412/0.8);--yellow-surface:color(display-p3 0.9961 0.9922 0.902/0.8);--amber-surface:color(display-p3 0.9922 0.9843 0.902/0.8);--orange-surface:color(display-p3 0.9961 0.9608 0.9176/0.8)}}}}.dark,.dark-theme{--gray-surface:#21212180;--mauve-surface:#22212380;--slate-surface:#1f212380;--sage-surface:#1e201f80;--olive-surface:#1f201e80;--sand-surface:#21212080;--tomato-surface:#2d191580;--red-surface:#2f151780;--ruby-surface:#2b191d80;--crimson-surface:#2f151f80;--pink-surface:#31132980;--plum-surface:#2f152f80;--purple-surface:#2b173580;--violet-surface:#25193980;--iris-surface:#1d1b3980;--indigo-surface:#171d3b80;--blue-surface:#11213d80;--cyan-surface:#11252d80;--teal-surface:#13272580;--jade-surface:#13271f80;--green-surface:#15251d80;--grass-surface:#19231b80;--brown-surface:#271f1b80;--bronze-surface:#27211d80;--gold-surface:#25231d80;--sky-surface:#13233b80;--mint-surface:#15272780;--lime-surface:#1b211580;--yellow-surface:#231f1380;--amber-surface:#271f1380;--orange-surface:#271d1380;@supports (color:color(display-p3 1 1 1)){@media (color-gamut:p3){&{--gray-surface:color(display-p3 0.1255 0.1255 0.1255/0.5);--mauve-surface:color(display-p3 0.1333 0.1255 0.1333/0.5);--slate-surface:color(display-p3 0.1176 0.1255 0.1333/0.5);--sage-surface:color(display-p3 0.1176 0.1255 0.1176/0.5);--olive-surface:color(display-p3 0.1176 0.1255 0.1176/0.5);--sand-surface:color(display-p3 0.1255 0.1255 0.1255/0.5);--tomato-surface:color(display-p3 0.1569 0.0941 0.0784/0.5);--red-surface:color(display-p3 0.1647 0.0863 0.0863/0.5);--ruby-surface:color(display-p3 0.1569 0.0941 0.1098/0.5);--crimson-surface:color(display-p3 0.1647 0.0863 0.1176/0.5);--pink-surface:color(display-p3 0.1725 0.0784 0.149/0.5);--plum-surface:color(display-p3 0.1647 0.0863 0.1725/0.5);--purple-surface:color(display-p3 0.149 0.0941 0.1961/0.5);--violet-surface:color(display-p3 0.1333 0.102 0.2118/0.5);--iris-surface:color(display-p3 0.1098 0.102 0.2118/0.5);--indigo-surface:color(display-p3 0.0941 0.1098 0.2196/0.5);--blue-surface:color(display-p3 0.0706 0.1255 0.2196/0.5);--cyan-surface:color(display-p3 0.0784 0.1412 0.1725/0.5);--teal-surface:color(display-p3 0.0863 0.149 0.1412/0.5);--jade-surface:color(display-p3 0.0863 0.149 0.1176/0.5);--green-surface:color(display-p3 0.0941 0.1412 0.1098/0.5);--grass-surface:color(display-p3 0.102 0.1333 0.102/0.5);--brown-surface:color(display-p3 0.1412 0.1176 0.102/0.5);--bronze-surface:color(display-p3 0.1412 0.1255 0.1176/0.5);--gold-surface:color(display-p3 0.1412 0.1333 0.1098/0.5);--sky-surface:color(display-p3 0.0863 0.1333 0.2196/0.5);--mint-surface:color(display-p3 0.0941 0.149 0.1412/0.5);--lime-surface:color(display-p3 0.1098 0.1255 0.0784/0.5);--yellow-surface:color(display-p3 0.1333 0.1176 0.0706/0.5);--amber-surface:color(display-p3 0.1412 0.1176 0.0784/0.5);--orange-surface:color(display-p3 0.1412 0.1098 0.0706/0.5)}}}}[data-accent-color=tomato]{--color-surface-accent:var(--tomato-surface);--accent-1:var(--tomato-1);--accent-2:var(--tomato-2);--accent-3:var(--tomato-3);--accent-4:var(--tomato-4);--accent-5:var(--tomato-5);--accent-6:var(--tomato-6);--accent-7:var(--tomato-7);--accent-8:var(--tomato-8);--accent-9:var(--tomato-9);--accent-9-contrast:var(--tomato-9-contrast);--accent-10:var(--tomato-10);--accent-11:var(--tomato-11);--accent-12:var(--tomato-12);--accent-a1:var(--tomato-a1);--accent-a2:var(--tomato-a2);--accent-a3:var(--tomato-a3);--accent-a4:var(--tomato-a4);--accent-a5:var(--tomato-a5);--accent-a6:var(--tomato-a6);--accent-a7:var(--tomato-a7);--accent-a8:var(--tomato-a8);--accent-a9:var(--tomato-a9);--accent-a10:var(--tomato-a10);--accent-a11:var(--tomato-a11);--accent-a12:var(--tomato-a12)}[data-accent-color=red]{--color-surface-accent:var(--red-surface);--accent-1:var(--red-1);--accent-2:var(--red-2);--accent-3:var(--red-3);--accent-4:var(--red-4);--accent-5:var(--red-5);--accent-6:var(--red-6);--accent-7:var(--red-7);--accent-8:var(--red-8);--accent-9:var(--red-9);--accent-9-contrast:var(--red-9-contrast);--accent-10:var(--red-10);--accent-11:var(--red-11);--accent-12:var(--red-12);--accent-a1:var(--red-a1);--accent-a2:var(--red-a2);--accent-a3:var(--red-a3);--accent-a4:var(--red-a4);--accent-a5:var(--red-a5);--accent-a6:var(--red-a6);--accent-a7:var(--red-a7);--accent-a8:var(--red-a8);--accent-a9:var(--red-a9);--accent-a10:var(--red-a10);--accent-a11:var(--red-a11);--accent-a12:var(--red-a12)}[data-accent-color=ruby]{--color-surface-accent:var(--ruby-surface);--accent-1:var(--ruby-1);--accent-2:var(--ruby-2);--accent-3:var(--ruby-3);--accent-4:var(--ruby-4);--accent-5:var(--ruby-5);--accent-6:var(--ruby-6);--accent-7:var(--ruby-7);--accent-8:var(--ruby-8);--accent-9:var(--ruby-9);--accent-9-contrast:var(--ruby-9-contrast);--accent-10:var(--ruby-10);--accent-11:var(--ruby-11);--accent-12:var(--ruby-12);--accent-a1:var(--ruby-a1);--accent-a2:var(--ruby-a2);--accent-a3:var(--ruby-a3);--accent-a4:var(--ruby-a4);--accent-a5:var(--ruby-a5);--accent-a6:var(--ruby-a6);--accent-a7:var(--ruby-a7);--accent-a8:var(--ruby-a8);--accent-a9:var(--ruby-a9);--accent-a10:var(--ruby-a10);--accent-a11:var(--ruby-a11);--accent-a12:var(--ruby-a12)}[data-accent-color=crimson]{--color-surface-accent:var(--crimson-surface);--accent-1:var(--crimson-1);--accent-2:var(--crimson-2);--accent-3:var(--crimson-3);--accent-4:var(--crimson-4);--accent-5:var(--crimson-5);--accent-6:var(--crimson-6);--accent-7:var(--crimson-7);--accent-8:var(--crimson-8);--accent-9:var(--crimson-9);--accent-9-contrast:var(--crimson-9-contrast);--accent-10:var(--crimson-10);--accent-11:var(--crimson-11);--accent-12:var(--crimson-12);--accent-a1:var(--crimson-a1);--accent-a2:var(--crimson-a2);--accent-a3:var(--crimson-a3);--accent-a4:var(--crimson-a4);--accent-a5:var(--crimson-a5);--accent-a6:var(--crimson-a6);--accent-a7:var(--crimson-a7);--accent-a8:var(--crimson-a8);--accent-a9:var(--crimson-a9);--accent-a10:var(--crimson-a10);--accent-a11:var(--crimson-a11);--accent-a12:var(--crimson-a12)}[data-accent-color=pink]{--color-surface-accent:var(--pink-surface);--accent-1:var(--pink-1);--accent-2:var(--pink-2);--accent-3:var(--pink-3);--accent-4:var(--pink-4);--accent-5:var(--pink-5);--accent-6:var(--pink-6);--accent-7:var(--pink-7);--accent-8:var(--pink-8);--accent-9:var(--pink-9);--accent-9-contrast:var(--pink-9-contrast);--accent-10:var(--pink-10);--accent-11:var(--pink-11);--accent-12:var(--pink-12);--accent-a1:var(--pink-a1);--accent-a2:var(--pink-a2);--accent-a3:var(--pink-a3);--accent-a4:var(--pink-a4);--accent-a5:var(--pink-a5);--accent-a6:var(--pink-a6);--accent-a7:var(--pink-a7);--accent-a8:var(--pink-a8);--accent-a9:var(--pink-a9);--accent-a10:var(--pink-a10);--accent-a11:var(--pink-a11);--accent-a12:var(--pink-a12)}[data-accent-color=plum]{--color-surface-accent:var(--plum-surface);--accent-1:var(--plum-1);--accent-2:var(--plum-2);--accent-3:var(--plum-3);--accent-4:var(--plum-4);--accent-5:var(--plum-5);--accent-6:var(--plum-6);--accent-7:var(--plum-7);--accent-8:var(--plum-8);--accent-9:var(--plum-9);--accent-9-contrast:var(--plum-9-contrast);--accent-10:var(--plum-10);--accent-11:var(--plum-11);--accent-12:var(--plum-12);--accent-a1:var(--plum-a1);--accent-a2:var(--plum-a2);--accent-a3:var(--plum-a3);--accent-a4:var(--plum-a4);--accent-a5:var(--plum-a5);--accent-a6:var(--plum-a6);--accent-a7:var(--plum-a7);--accent-a8:var(--plum-a8);--accent-a9:var(--plum-a9);--accent-a10:var(--plum-a10);--accent-a11:var(--plum-a11);--accent-a12:var(--plum-a12)}[data-accent-color=purple]{--color-surface-accent:var(--purple-surface);--accent-1:var(--purple-1);--accent-2:var(--purple-2);--accent-3:var(--purple-3);--accent-4:var(--purple-4);--accent-5:var(--purple-5);--accent-6:var(--purple-6);--accent-7:var(--purple-7);--accent-8:var(--purple-8);--accent-9:var(--purple-9);--accent-9-contrast:var(--purple-9-contrast);--accent-10:var(--purple-10);--accent-11:var(--purple-11);--accent-12:var(--purple-12);--accent-a1:var(--purple-a1);--accent-a2:var(--purple-a2);--accent-a3:var(--purple-a3);--accent-a4:var(--purple-a4);--accent-a5:var(--purple-a5);--accent-a6:var(--purple-a6);--accent-a7:var(--purple-a7);--accent-a8:var(--purple-a8);--accent-a9:var(--purple-a9);--accent-a10:var(--purple-a10);--accent-a11:var(--purple-a11);--accent-a12:var(--purple-a12)}[data-accent-color=violet]{--color-surface-accent:var(--violet-surface);--accent-1:var(--violet-1);--accent-2:var(--violet-2);--accent-3:var(--violet-3);--accent-4:var(--violet-4);--accent-5:var(--violet-5);--accent-6:var(--violet-6);--accent-7:var(--violet-7);--accent-8:var(--violet-8);--accent-9:var(--violet-9);--accent-9-contrast:var(--violet-9-contrast);--accent-10:var(--violet-10);--accent-11:var(--violet-11);--accent-12:var(--violet-12);--accent-a1:var(--violet-a1);--accent-a2:var(--violet-a2);--accent-a3:var(--violet-a3);--accent-a4:var(--violet-a4);--accent-a5:var(--violet-a5);--accent-a6:var(--violet-a6);--accent-a7:var(--violet-a7);--accent-a8:var(--violet-a8);--accent-a9:var(--violet-a9);--accent-a10:var(--violet-a10);--accent-a11:var(--violet-a11);--accent-a12:var(--violet-a12)}[data-accent-color=iris]{--color-surface-accent:var(--iris-surface);--accent-1:var(--iris-1);--accent-2:var(--iris-2);--accent-3:var(--iris-3);--accent-4:var(--iris-4);--accent-5:var(--iris-5);--accent-6:var(--iris-6);--accent-7:var(--iris-7);--accent-8:var(--iris-8);--accent-9:var(--iris-9);--accent-9-contrast:var(--iris-9-contrast);--accent-10:var(--iris-10);--accent-11:var(--iris-11);--accent-12:var(--iris-12);--accent-a1:var(--iris-a1);--accent-a2:var(--iris-a2);--accent-a3:var(--iris-a3);--accent-a4:var(--iris-a4);--accent-a5:var(--iris-a5);--accent-a6:var(--iris-a6);--accent-a7:var(--iris-a7);--accent-a8:var(--iris-a8);--accent-a9:var(--iris-a9);--accent-a10:var(--iris-a10);--accent-a11:var(--iris-a11);--accent-a12:var(--iris-a12)}[data-accent-color=indigo]{--color-surface-accent:var(--indigo-surface);--accent-1:var(--indigo-1);--accent-2:var(--indigo-2);--accent-3:var(--indigo-3);--accent-4:var(--indigo-4);--accent-5:var(--indigo-5);--accent-6:var(--indigo-6);--accent-7:var(--indigo-7);--accent-8:var(--indigo-8);--accent-9:var(--indigo-9);--accent-9-contrast:var(--indigo-9-contrast);--accent-10:var(--indigo-10);--accent-11:var(--indigo-11);--accent-12:var(--indigo-12);--accent-a1:var(--indigo-a1);--accent-a2:var(--indigo-a2);--accent-a3:var(--indigo-a3);--accent-a4:var(--indigo-a4);--accent-a5:var(--indigo-a5);--accent-a6:var(--indigo-a6);--accent-a7:var(--indigo-a7);--accent-a8:var(--indigo-a8);--accent-a9:var(--indigo-a9);--accent-a10:var(--indigo-a10);--accent-a11:var(--indigo-a11);--accent-a12:var(--indigo-a12)}[data-accent-color=blue]{--color-surface-accent:var(--blue-surface);--accent-1:var(--blue-1);--accent-2:var(--blue-2);--accent-3:var(--blue-3);--accent-4:var(--blue-4);--accent-5:var(--blue-5);--accent-6:var(--blue-6);--accent-7:var(--blue-7);--accent-8:var(--blue-8);--accent-9:var(--blue-9);--accent-9-contrast:var(--blue-9-contrast);--accent-10:var(--blue-10);--accent-11:var(--blue-11);--accent-12:var(--blue-12);--accent-a1:var(--blue-a1);--accent-a2:var(--blue-a2);--accent-a3:var(--blue-a3);--accent-a4:var(--blue-a4);--accent-a5:var(--blue-a5);--accent-a6:var(--blue-a6);--accent-a7:var(--blue-a7);--accent-a8:var(--blue-a8);--accent-a9:var(--blue-a9);--accent-a10:var(--blue-a10);--accent-a11:var(--blue-a11);--accent-a12:var(--blue-a12)}[data-accent-color=cyan]{--color-surface-accent:var(--cyan-surface);--accent-1:var(--cyan-1);--accent-2:var(--cyan-2);--accent-3:var(--cyan-3);--accent-4:var(--cyan-4);--accent-5:var(--cyan-5);--accent-6:var(--cyan-6);--accent-7:var(--cyan-7);--accent-8:var(--cyan-8);--accent-9:var(--cyan-9);--accent-9-contrast:var(--cyan-9-contrast);--accent-10:var(--cyan-10);--accent-11:var(--cyan-11);--accent-12:var(--cyan-12);--accent-a1:var(--cyan-a1);--accent-a2:var(--cyan-a2);--accent-a3:var(--cyan-a3);--accent-a4:var(--cyan-a4);--accent-a5:var(--cyan-a5);--accent-a6:var(--cyan-a6);--accent-a7:var(--cyan-a7);--accent-a8:var(--cyan-a8);--accent-a9:var(--cyan-a9);--accent-a10:var(--cyan-a10);--accent-a11:var(--cyan-a11);--accent-a12:var(--cyan-a12)}[data-accent-color=teal]{--color-surface-accent:var(--teal-surface);--accent-1:var(--teal-1);--accent-2:var(--teal-2);--accent-3:var(--teal-3);--accent-4:var(--teal-4);--accent-5:var(--teal-5);--accent-6:var(--teal-6);--accent-7:var(--teal-7);--accent-8:var(--teal-8);--accent-9:var(--teal-9);--accent-9-contrast:var(--teal-9-contrast);--accent-10:var(--teal-10);--accent-11:var(--teal-11);--accent-12:var(--teal-12);--accent-a1:var(--teal-a1);--accent-a2:var(--teal-a2);--accent-a3:var(--teal-a3);--accent-a4:var(--teal-a4);--accent-a5:var(--teal-a5);--accent-a6:var(--teal-a6);--accent-a7:var(--teal-a7);--accent-a8:var(--teal-a8);--accent-a9:var(--teal-a9);--accent-a10:var(--teal-a10);--accent-a11:var(--teal-a11);--accent-a12:var(--teal-a12)}[data-accent-color=jade]{--color-surface-accent:var(--jade-surface);--accent-1:var(--jade-1);--accent-2:var(--jade-2);--accent-3:var(--jade-3);--accent-4:var(--jade-4);--accent-5:var(--jade-5);--accent-6:var(--jade-6);--accent-7:var(--jade-7);--accent-8:var(--jade-8);--accent-9:var(--jade-9);--accent-9-contrast:var(--jade-9-contrast);--accent-10:var(--jade-10);--accent-11:var(--jade-11);--accent-12:var(--jade-12);--accent-a1:var(--jade-a1);--accent-a2:var(--jade-a2);--accent-a3:var(--jade-a3);--accent-a4:var(--jade-a4);--accent-a5:var(--jade-a5);--accent-a6:var(--jade-a6);--accent-a7:var(--jade-a7);--accent-a8:var(--jade-a8);--accent-a9:var(--jade-a9);--accent-a10:var(--jade-a10);--accent-a11:var(--jade-a11);--accent-a12:var(--jade-a12)}[data-accent-color=green]{--color-surface-accent:var(--green-surface);--accent-1:var(--green-1);--accent-2:var(--green-2);--accent-3:var(--green-3);--accent-4:var(--green-4);--accent-5:var(--green-5);--accent-6:var(--green-6);--accent-7:var(--green-7);--accent-8:var(--green-8);--accent-9:var(--green-9);--accent-9-contrast:var(--green-9-contrast);--accent-10:var(--green-10);--accent-11:var(--green-11);--accent-12:var(--green-12);--accent-a1:var(--green-a1);--accent-a2:var(--green-a2);--accent-a3:var(--green-a3);--accent-a4:var(--green-a4);--accent-a5:var(--green-a5);--accent-a6:var(--green-a6);--accent-a7:var(--green-a7);--accent-a8:var(--green-a8);--accent-a9:var(--green-a9);--accent-a10:var(--green-a10);--accent-a11:var(--green-a11);--accent-a12:var(--green-a12)}[data-accent-color=grass]{--color-surface-accent:var(--grass-surface);--accent-1:var(--grass-1);--accent-2:var(--grass-2);--accent-3:var(--grass-3);--accent-4:var(--grass-4);--accent-5:var(--grass-5);--accent-6:var(--grass-6);--accent-7:var(--grass-7);--accent-8:var(--grass-8);--accent-9:var(--grass-9);--accent-9-contrast:var(--grass-9-contrast);--accent-10:var(--grass-10);--accent-11:var(--grass-11);--accent-12:var(--grass-12);--accent-a1:var(--grass-a1);--accent-a2:var(--grass-a2);--accent-a3:var(--grass-a3);--accent-a4:var(--grass-a4);--accent-a5:var(--grass-a5);--accent-a6:var(--grass-a6);--accent-a7:var(--grass-a7);--accent-a8:var(--grass-a8);--accent-a9:var(--grass-a9);--accent-a10:var(--grass-a10);--accent-a11:var(--grass-a11);--accent-a12:var(--grass-a12)}[data-accent-color=orange]{--color-surface-accent:var(--orange-surface);--accent-1:var(--orange-1);--accent-2:var(--orange-2);--accent-3:var(--orange-3);--accent-4:var(--orange-4);--accent-5:var(--orange-5);--accent-6:var(--orange-6);--accent-7:var(--orange-7);--accent-8:var(--orange-8);--accent-9:var(--orange-9);--accent-9-contrast:var(--orange-9-contrast);--accent-10:var(--orange-10);--accent-11:var(--orange-11);--accent-12:var(--orange-12);--accent-a1:var(--orange-a1);--accent-a2:var(--orange-a2);--accent-a3:var(--orange-a3);--accent-a4:var(--orange-a4);--accent-a5:var(--orange-a5);--accent-a6:var(--orange-a6);--accent-a7:var(--orange-a7);--accent-a8:var(--orange-a8);--accent-a9:var(--orange-a9);--accent-a10:var(--orange-a10);--accent-a11:var(--orange-a11);--accent-a12:var(--orange-a12)}[data-accent-color=brown]{--color-surface-accent:var(--brown-surface);--accent-1:var(--brown-1);--accent-2:var(--brown-2);--accent-3:var(--brown-3);--accent-4:var(--brown-4);--accent-5:var(--brown-5);--accent-6:var(--brown-6);--accent-7:var(--brown-7);--accent-8:var(--brown-8);--accent-9:var(--brown-9);--accent-9-contrast:var(--brown-9-contrast);--accent-10:var(--brown-10);--accent-11:var(--brown-11);--accent-12:var(--brown-12);--accent-a1:var(--brown-a1);--accent-a2:var(--brown-a2);--accent-a3:var(--brown-a3);--accent-a4:var(--brown-a4);--accent-a5:var(--brown-a5);--accent-a6:var(--brown-a6);--accent-a7:var(--brown-a7);--accent-a8:var(--brown-a8);--accent-a9:var(--brown-a9);--accent-a10:var(--brown-a10);--accent-a11:var(--brown-a11);--accent-a12:var(--brown-a12)}[data-accent-color=sky]{--color-surface-accent:var(--sky-surface);--accent-1:var(--sky-1);--accent-2:var(--sky-2);--accent-3:var(--sky-3);--accent-4:var(--sky-4);--accent-5:var(--sky-5);--accent-6:var(--sky-6);--accent-7:var(--sky-7);--accent-8:var(--sky-8);--accent-9:var(--sky-9);--accent-9-contrast:var(--sky-9-contrast);--accent-10:var(--sky-10);--accent-11:var(--sky-11);--accent-12:var(--sky-12);--accent-a1:var(--sky-a1);--accent-a2:var(--sky-a2);--accent-a3:var(--sky-a3);--accent-a4:var(--sky-a4);--accent-a5:var(--sky-a5);--accent-a6:var(--sky-a6);--accent-a7:var(--sky-a7);--accent-a8:var(--sky-a8);--accent-a9:var(--sky-a9);--accent-a10:var(--sky-a10);--accent-a11:var(--sky-a11);--accent-a12:var(--sky-a12)}[data-accent-color=mint]{--color-surface-accent:var(--mint-surface);--accent-1:var(--mint-1);--accent-2:var(--mint-2);--accent-3:var(--mint-3);--accent-4:var(--mint-4);--accent-5:var(--mint-5);--accent-6:var(--mint-6);--accent-7:var(--mint-7);--accent-8:var(--mint-8);--accent-9:var(--mint-9);--accent-9-contrast:var(--mint-9-contrast);--accent-10:var(--mint-10);--accent-11:var(--mint-11);--accent-12:var(--mint-12);--accent-a1:var(--mint-a1);--accent-a2:var(--mint-a2);--accent-a3:var(--mint-a3);--accent-a4:var(--mint-a4);--accent-a5:var(--mint-a5);--accent-a6:var(--mint-a6);--accent-a7:var(--mint-a7);--accent-a8:var(--mint-a8);--accent-a9:var(--mint-a9);--accent-a10:var(--mint-a10);--accent-a11:var(--mint-a11);--accent-a12:var(--mint-a12)}[data-accent-color=lime]{--color-surface-accent:var(--lime-surface);--accent-1:var(--lime-1);--accent-2:var(--lime-2);--accent-3:var(--lime-3);--accent-4:var(--lime-4);--accent-5:var(--lime-5);--accent-6:var(--lime-6);--accent-7:var(--lime-7);--accent-8:var(--lime-8);--accent-9:var(--lime-9);--accent-9-contrast:var(--lime-9-contrast);--accent-10:var(--lime-10);--accent-11:var(--lime-11);--accent-12:var(--lime-12);--accent-a1:var(--lime-a1);--accent-a2:var(--lime-a2);--accent-a3:var(--lime-a3);--accent-a4:var(--lime-a4);--accent-a5:var(--lime-a5);--accent-a6:var(--lime-a6);--accent-a7:var(--lime-a7);--accent-a8:var(--lime-a8);--accent-a9:var(--lime-a9);--accent-a10:var(--lime-a10);--accent-a11:var(--lime-a11);--accent-a12:var(--lime-a12)}[data-accent-color=yellow]{--color-surface-accent:var(--yellow-surface);--accent-1:var(--yellow-1);--accent-2:var(--yellow-2);--accent-3:var(--yellow-3);--accent-4:var(--yellow-4);--accent-5:var(--yellow-5);--accent-6:var(--yellow-6);--accent-7:var(--yellow-7);--accent-8:var(--yellow-8);--accent-9:var(--yellow-9);--accent-9-contrast:var(--yellow-9-contrast);--accent-10:var(--yellow-10);--accent-11:var(--yellow-11);--accent-12:var(--yellow-12);--accent-a1:var(--yellow-a1);--accent-a2:var(--yellow-a2);--accent-a3:var(--yellow-a3);--accent-a4:var(--yellow-a4);--accent-a5:var(--yellow-a5);--accent-a6:var(--yellow-a6);--accent-a7:var(--yellow-a7);--accent-a8:var(--yellow-a8);--accent-a9:var(--yellow-a9);--accent-a10:var(--yellow-a10);--accent-a11:var(--yellow-a11);--accent-a12:var(--yellow-a12)}[data-accent-color=amber]{--color-surface-accent:var(--amber-surface);--accent-1:var(--amber-1);--accent-2:var(--amber-2);--accent-3:var(--amber-3);--accent-4:var(--amber-4);--accent-5:var(--amber-5);--accent-6:var(--amber-6);--accent-7:var(--amber-7);--accent-8:var(--amber-8);--accent-9:var(--amber-9);--accent-9-contrast:var(--amber-9-contrast);--accent-10:var(--amber-10);--accent-11:var(--amber-11);--accent-12:var(--amber-12);--accent-a1:var(--amber-a1);--accent-a2:var(--amber-a2);--accent-a3:var(--amber-a3);--accent-a4:var(--amber-a4);--accent-a5:var(--amber-a5);--accent-a6:var(--amber-a6);--accent-a7:var(--amber-a7);--accent-a8:var(--amber-a8);--accent-a9:var(--amber-a9);--accent-a10:var(--amber-a10);--accent-a11:var(--amber-a11);--accent-a12:var(--amber-a12)}[data-accent-color=gold]{--color-surface-accent:var(--gold-surface);--accent-1:var(--gold-1);--accent-2:var(--gold-2);--accent-3:var(--gold-3);--accent-4:var(--gold-4);--accent-5:var(--gold-5);--accent-6:var(--gold-6);--accent-7:var(--gold-7);--accent-8:var(--gold-8);--accent-9:var(--gold-9);--accent-9-contrast:var(--gold-9-contrast);--accent-10:var(--gold-10);--accent-11:var(--gold-11);--accent-12:var(--gold-12);--accent-a1:var(--gold-a1);--accent-a2:var(--gold-a2);--accent-a3:var(--gold-a3);--accent-a4:var(--gold-a4);--accent-a5:var(--gold-a5);--accent-a6:var(--gold-a6);--accent-a7:var(--gold-a7);--accent-a8:var(--gold-a8);--accent-a9:var(--gold-a9);--accent-a10:var(--gold-a10);--accent-a11:var(--gold-a11);--accent-a12:var(--gold-a12)}[data-accent-color=bronze]{--color-surface-accent:var(--bronze-surface);--accent-1:var(--bronze-1);--accent-2:var(--bronze-2);--accent-3:var(--bronze-3);--accent-4:var(--bronze-4);--accent-5:var(--bronze-5);--accent-6:var(--bronze-6);--accent-7:var(--bronze-7);--accent-8:var(--bronze-8);--accent-9:var(--bronze-9);--accent-9-contrast:var(--bronze-9-contrast);--accent-10:var(--bronze-10);--accent-11:var(--bronze-11);--accent-12:var(--bronze-12);--accent-a1:var(--bronze-a1);--accent-a2:var(--bronze-a2);--accent-a3:var(--bronze-a3);--accent-a4:var(--bronze-a4);--accent-a5:var(--bronze-a5);--accent-a6:var(--bronze-a6);--accent-a7:var(--bronze-a7);--accent-a8:var(--bronze-a8);--accent-a9:var(--bronze-a9);--accent-a10:var(--bronze-a10);--accent-a11:var(--bronze-a11);--accent-a12:var(--bronze-a12)}[data-accent-color=gray]{--color-surface-accent:var(--gray-surface);--accent-1:var(--gray-1);--accent-2:var(--gray-2);--accent-3:var(--gray-3);--accent-4:var(--gray-4);--accent-5:var(--gray-5);--accent-6:var(--gray-6);--accent-7:var(--gray-7);--accent-8:var(--gray-8);--accent-9:var(--gray-9);--accent-9-contrast:var(--gray-9-contrast);--accent-10:var(--gray-10);--accent-11:var(--gray-11);--accent-12:var(--gray-12);--accent-a1:var(--gray-a1);--accent-a2:var(--gray-a2);--accent-a3:var(--gray-a3);--accent-a4:var(--gray-a4);--accent-a5:var(--gray-a5);--accent-a6:var(--gray-a6);--accent-a7:var(--gray-a7);--accent-a8:var(--gray-a8);--accent-a9:var(--gray-a9);--accent-a10:var(--gray-a10);--accent-a11:var(--gray-a11);--accent-a12:var(--gray-a12)}:root{--sy-f-sys:-apple-system,BlinkMacSystemFont,Segoe UI,Oxygen,Ubuntu,Droid Sans,Helvetica Neue;--sy-f-cjk:PingFang SC,Hiragino Sans GB,Droid Sans Fallback,Microsoft YaHei;--sy-f-heading:var(--sy-f-sys),var(--sy-f-cjk),sans-serif;--sy-f-text:var(--sy-f-sys),var(--sy-f-cjk),sans-serif;--sy-f-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--sy-s-banner-height:0rem;--sy-s-navbar-height:56px;--sy-s-offset-top:calc(var(--sy-s-navbar-height) + var(--sy-s-banner-height));--sy-c-divider:var(--gray-4);--sy-c-border:var(--gray-5);--sy-c-text:var(--gray-12);--sy-c-light:var(--gray-11);--sy-c-bold:var(--slate-12);--sy-c-heading:var(--sage-12);--sy-c-link:var(--accent-9);--sy-c-link-hover:var(--accent-a11);--sy-c-background:#fff;--sy-c-surface:var(--gray-a2);--sy-c-overlay:var(--black-a5);--sy-c-background-contrast:#000;--sy-c-foot-text:var(--sy-c-text);--sy-c-foot-background:var(--slate-2);--sy-c-foot-divider:var(--sy-c-divider);--sy-dropdown-shadow:0 12px 32px var(--gray-a6),0 2px 6px var(--gray-a4)}[data-accent-color=amber],[data-accent-color=lime],[data-accent-color=mint],[data-accent-color=sky],[data-accent-color=yellow]{--sy-c-link:var(--accent-a10)}html.light{color-scheme:light;--sy-c-background:#fff;--sy-c-background-contrast:#000}html.dark{color-scheme:dark;--sy-c-background:var(--slate-1);--sy-c-overlay:var(--white-a2);--sy-c-background-contrast:var(--white-a10);--sy-c-foot-background:var(--black-a11);--sy-c-foot-divider:var(--black-a12)}html{color:var(--sy-c-text);background-color:var(--sy-c-background)}:root{--lucide-alert-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3zM12 9v4m0 4h.01'/%3E%3C/svg%3E");--lucide-arrows-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='m7 15 5 5 5-5M7 9l5-5 5 5'/%3E%3C/svg%3E");--lucide-award-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Ccircle cx='12' cy='8' r='6'/%3E%3Cpath d='M15.477 12.89 17 22l-5-3-5 3 1.523-9.11'/%3E%3C/svg%3E");--lucide-bell-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9m-4.27 13a2 2 0 0 1-3.46 0'/%3E%3C/svg%3E");--lucide-bookmark-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z'/%3E%3C/svg%3E");--lucide-calendar-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='18' height='18' x='3' y='4' rx='2' ry='2'/%3E%3Cpath d='M16 2v4M8 2v4m-5 4h18'/%3E%3C/svg%3E");--lucide-check-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");--lucide-chevron-down-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' class='lucide lucide-chevron-down'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");--lucide-chevron-left-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' class='lucide lucide-chevron-left'%3E%3Cpath d='m15 18-6-6 6-6'/%3E%3C/svg%3E");--lucide-chevron-right-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' class='lucide lucide-chevron-right'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E");--lucide-chevron-up-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' class='lucide lucide-chevron-up'%3E%3Cpath d='m18 15-6-6-6 6'/%3E%3C/svg%3E");--lucide-close-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M18 6 6 18M6 6l12 12'/%3E%3C/svg%3E");--lucide-external-link-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M13 5h6v6m0-6L5 19'/%3E%3C/svg%3E");--lucide-flame-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z'/%3E%3C/svg%3E");--lucide-git-fork-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Ccircle cx='12' cy='18' r='3'/%3E%3Ccircle cx='6' cy='6' r='3'/%3E%3Ccircle cx='18' cy='6' r='3'/%3E%3Cpath d='M18 9v1a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V9m6 3v3'/%3E%3C/svg%3E");--lucide-help-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3m.08 4h.01'/%3E%3C/svg%3E");--lucide-languages-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' class='lucide lucide-languages'%3E%3Cpath d='m5 8 6 6m-7 0 6-6 2-3M2 5h12M7 2h1m14 20-5-10-5 10m2-4h6'/%3E%3C/svg%3E");--lucide-laptop-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='18' height='12' x='3' y='4' rx='2' ry='2'/%3E%3Cpath d='M2 20h20'/%3E%3C/svg%3E");--lucide-link-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71'/%3E%3Cpath d='M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71'/%3E%3C/svg%3E");--lucide-menu-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' class='lucide lucide-menu'%3E%3Cpath d='M4 12h16M4 6h16M4 18h16'/%3E%3C/svg%3E");--lucide-milestone-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M18 6H5a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h13l4-3.5L18 6zm-6 7v8m0-18v3'/%3E%3C/svg%3E");--lucide-moon-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M12 3a6.364 6.364 0 0 0 9 9 9 9 0 1 1-9-9z'/%3E%3C/svg%3E");--lucide-outdent-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='m7 8-4 4 4 4m14-4H11m10-6H11m10 12H11'/%3E%3C/svg%3E");--lucide-rocket-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09zM12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z'/%3E%3Cpath d='M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0m1 7v5s3.03-.55 4-2c1.08-1.62 0-5 0-5'/%3E%3C/svg%3E");--lucide-skull-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Ccircle cx='9' cy='12' r='1'/%3E%3Ccircle cx='15' cy='12' r='1'/%3E%3Cpath d='M8 20v2h8v-2m-3.5-3-.5-1-.5 1h1z'/%3E%3Cpath d='M16 20a2 2 0 0 0 1.56-3.25 8 8 0 1 0-11.12 0A2 2 0 0 0 8 20'/%3E%3C/svg%3E");--lucide-star-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='m12 2 3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z'/%3E%3C/svg%3E");--lucide-sun-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Ccircle cx='12' cy='12' r='4'/%3E%3Cpath d='M12 2v2m0 16v2M4.93 4.93l1.41 1.41m11.32 11.32 1.41 1.41M2 12h2m16 0h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41'/%3E%3C/svg%3E");--lucide-zap-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M13 2 3 14h9l-1 8 10-12h-9l1-8z'/%3E%3C/svg%3E")}.i-lucide.alert{--icon-url:var(--lucide-alert-url)}.i-lucide.arrows{--icon-url:var(--lucide-arrows-url)}.i-lucide.award{--icon-url:var(--lucide-award-url)}.i-lucide.bell{--icon-url:var(--lucide-bell-url)}.i-lucide.bookmark{--icon-url:var(--lucide-bookmark-url)}.i-lucide.calendar{--icon-url:var(--lucide-calendar-url)}.i-lucide.check{--icon-url:var(--lucide-check-url)}.i-lucide.chevron-down{--icon-url:var(--lucide-chevron-down-url)}.i-lucide.chevron-left{--icon-url:var(--lucide-chevron-left-url)}.i-lucide.chevron-right{--icon-url:var(--lucide-chevron-right-url)}.i-lucide.chevron-up{--icon-url:var(--lucide-chevron-up-url)}.i-lucide.close{--icon-url:var(--lucide-close-url)}.i-lucide.external-link{--icon-url:var(--lucide-external-link-url)}.i-lucide.flame{--icon-url:var(--lucide-flame-url)}.i-lucide.git-fork{--icon-url:var(--lucide-git-fork-url)}.i-lucide.help{--icon-url:var(--lucide-help-url)}.i-lucide.languages{--icon-url:var(--lucide-languages-url)}.i-lucide.laptop{--icon-url:var(--lucide-laptop-url)}.i-lucide.link{--icon-url:var(--lucide-link-url)}.i-lucide.menu{--icon-url:var(--lucide-menu-url)}.i-lucide.milestone{--icon-url:var(--lucide-milestone-url)}.i-lucide.moon{--icon-url:var(--lucide-moon-url)}.i-lucide.outdent{--icon-url:var(--lucide-outdent-url)}.i-lucide.rocket{--icon-url:var(--lucide-rocket-url)}.i-lucide.skull{--icon-url:var(--lucide-skull-url)}.i-lucide.star{--icon-url:var(--lucide-star-url)}.i-lucide.sun{--icon-url:var(--lucide-sun-url)}.i-lucide.zap{--icon-url:var(--lucide-zap-url)}:root{--simpleicons-bitbucket-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M.778 1.213a.768.768 0 0 0-.768.892l3.263 19.81c.084.5.515.868 1.022.873H19.95a.772.772 0 0 0 .77-.646l3.27-20.03a.768.768 0 0 0-.768-.891zM14.52 15.53H9.522L8.17 8.466h7.561z'/%3E%3C/svg%3E");--simpleicons-discord-url:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z'/%3E%3C/svg%3E");--simpleicons-git-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M23.546 10.93 13.067.452a1.55 1.55 0 0 0-2.188 0L8.708 2.627l2.76 2.76a1.838 1.838 0 0 1 2.327 2.341l2.658 2.66a1.838 1.838 0 0 1 1.9 3.039 1.837 1.837 0 0 1-2.6 0 1.846 1.846 0 0 1-.404-1.996L12.86 8.955v6.525c.176.086.342.203.488.348a1.848 1.848 0 0 1 0 2.6 1.844 1.844 0 0 1-2.609 0 1.834 1.834 0 0 1 0-2.598c.182-.18.387-.316.605-.406V8.835a1.834 1.834 0 0 1-.996-2.41L7.636 3.7.45 10.881c-.6.605-.6 1.584 0 2.189l10.48 10.477a1.545 1.545 0 0 0 2.186 0l10.43-10.43a1.544 1.544 0 0 0 0-2.187'/%3E%3C/svg%3E");--simpleicons-github-url:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E");--simpleicons-gitlab-url:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m23.6 9.593-.033-.086L20.3.98a.851.851 0 0 0-.336-.405.875.875 0 0 0-1 .054.875.875 0 0 0-.29.44L16.47 7.818H7.537L5.332 1.07a.857.857 0 0 0-.29-.441.875.875 0 0 0-1-.054.859.859 0 0 0-.336.405L.433 9.502l-.032.086a6.066 6.066 0 0 0 2.012 7.01l.01.009.03.021 4.977 3.727 2.462 1.863 1.5 1.132a1.008 1.008 0 0 0 1.22 0l1.499-1.132 2.461-1.863 5.006-3.75.013-.01a6.068 6.068 0 0 0 2.01-7.002z'/%3E%3C/svg%3E");--simpleicons-linkedin-url:url("data:image/svg+xml;utf8,%3Csvg role='img' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ctitle%3ELinkedIn%3C/title%3E%3Cpath d='M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z'/%3E%3C/svg%3E");--simpleicons-mastodon-url:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z'/%3E%3C/svg%3E");--simpleicons-readthedocs-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7.732 0a59.316 59.316 0 0 0-4.977.218V24a62.933 62.933 0 0 1 3.619-.687c.17-.028.34-.053.509-.078.215-.033.43-.066.644-.096l.205-.03zm1.18.003V22.96a61.042 61.042 0 0 1 12.333-.213V1.485A60.859 60.859 0 0 0 8.912.003zm1.707 1.81a.59.59 0 0 1 .015 0c3.06.088 6.125.404 9.167.95a.59.59 0 0 1 .476.686.59.59 0 0 1-.569.484.59.59 0 0 1-.116-.009 60.622 60.622 0 0 0-8.992-.931.59.59 0 0 1-.573-.607.59.59 0 0 1 .592-.572zm-4.212.028a.59.59 0 0 1 .578.565.59.59 0 0 1-.564.614 59.74 59.74 0 0 0-2.355.144.59.59 0 0 1-.04.002.59.59 0 0 1-.595-.542.59.59 0 0 1 .54-.635c.8-.065 1.6-.114 2.401-.148a.59.59 0 0 1 .035 0zm4.202 2.834a.59.59 0 0 1 .015 0 61.6 61.6 0 0 1 9.167.8.59.59 0 0 1 .488.677.59.59 0 0 1-.602.494.59.59 0 0 1-.076-.006 60.376 60.376 0 0 0-8.99-.786.59.59 0 0 1-.584-.596.59.59 0 0 1 .582-.583zm-4.211.097a.59.59 0 0 1 .587.555.59.59 0 0 1-.554.622c-.786.046-1.572.107-2.356.184a.59.59 0 0 1-.04.003.59.59 0 0 1-.603-.533.59.59 0 0 1 .53-.644c.8-.078 1.599-.14 2.4-.187a.59.59 0 0 1 .036 0zM10.6 7.535a.59.59 0 0 1 .015 0c3.06-.013 6.125.204 9.167.65a.59.59 0 0 1 .498.67.59.59 0 0 1-.593.504.59.59 0 0 1-.076-.006 60.142 60.142 0 0 0-8.992-.638.59.59 0 0 1-.592-.588.59.59 0 0 1 .573-.592zm1.153 2.846a61.093 61.093 0 0 1 8.02.515.59.59 0 0 1 .509.66.59.59 0 0 1-.586.514.59.59 0 0 1-.076-.005 59.982 59.982 0 0 0-8.99-.492.59.59 0 0 1-.603-.577.59.59 0 0 1 .578-.603c.382-.008.765-.012 1.148-.012zm1.139 2.832a60.92 60.92 0 0 1 6.871.394.59.59 0 0 1 .52.652.59.59 0 0 1-.577.523.59.59 0 0 1-.076-.004 59.936 59.936 0 0 0-8.991-.344.59.59 0 0 1-.61-.568.59.59 0 0 1 .567-.611c.765-.028 1.53-.042 2.296-.042z'/%3E%3C/svg%3E");--simpleicons-reddit-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 0C5.373 0 0 5.373 0 12c0 3.314 1.343 6.314 3.515 8.485l-2.286 2.286A.72.72 0 0 0 1.738 24H12c6.627 0 12-5.373 12-12S18.627 0 12 0Zm4.388 3.199a1.999 1.999 0 1 1-1.947 2.46v.002a2.368 2.368 0 0 0-2.032 2.341v.007c1.776.067 3.4.567 4.686 1.363a2.802 2.802 0 1 1 2.908 4.753c-.088 3.256-3.637 5.876-7.997 5.876-4.361 0-7.905-2.617-7.998-5.87a2.8 2.8 0 0 1 1.189-5.34c.645 0 1.239.218 1.712.585 1.275-.79 2.881-1.291 4.64-1.365v-.01a3.229 3.229 0 0 1 2.88-3.207 2 2 0 0 1 1.959-1.595Zm-8.085 8.376c-.784 0-1.459.78-1.506 1.797-.047 1.016.64 1.429 1.426 1.429.786 0 1.371-.369 1.418-1.385.047-1.017-.553-1.841-1.338-1.841Zm7.406 0c-.786 0-1.385.824-1.338 1.841.047 1.017.634 1.385 1.418 1.385.785 0 1.473-.413 1.426-1.429-.046-1.017-.721-1.797-1.506-1.797Zm-3.703 4.013c-.974 0-1.907.048-2.77.135a.222.222 0 0 0-.183.305 3.199 3.199 0 0 0 2.953 1.964 3.2 3.2 0 0 0 2.953-1.964.222.222 0 0 0-.184-.305 27.75 27.75 0 0 0-2.769-.135Z'/%3E%3C/svg%3E");--simpleicons-slack-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zm10.122 2.521a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zm-1.268 0a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zm-2.523 10.122a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zm0-1.268a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z'/%3E%3C/svg%3E");--simpleicons-x-twitter-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z'/%3E%3C/svg%3E");--simpleicons-youtube-url:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z'/%3E%3C/svg%3E")}.i-icon.bitbucket,.i-simpleicons.bitbucket{--icon-url:var(--simpleicons-bitbucket-url)}.i-icon.discord,.i-simpleicons.discord{--icon-url:var(--simpleicons-discord-url)}.i-icon.git,.i-simpleicons.git{--icon-url:var(--simpleicons-git-url)}.i-icon.github,.i-simpleicons.github{--icon-url:var(--simpleicons-github-url)}.i-icon.gitlab,.i-simpleicons.gitlab{--icon-url:var(--simpleicons-gitlab-url)}.i-icon.linkedin,.i-simpleicons.linkedin{--icon-url:var(--simpleicons-linkedin-url)}.i-icon.mastodon,.i-simpleicons.mastodon{--icon-url:var(--simpleicons-mastodon-url)}.i-icon.readthedocs,.i-simpleicons.readthedocs{--icon-url:var(--simpleicons-readthedocs-url)}.i-icon.reddit,.i-simpleicons.reddit{--icon-url:var(--simpleicons-reddit-url)}.i-icon.slack,.i-simpleicons.slack{--icon-url:var(--simpleicons-slack-url)}.i-icon.x-twitter,.i-simpleicons.x-twitter{--icon-url:var(--simpleicons-x-twitter-url)}.i-icon.youtube,.i-simpleicons.youtube{--icon-url:var(--simpleicons-youtube-url)}:root{--yue-c-text:var(--sy-c-text);--yue-c-heading:var(--sy-c-heading);--yue-c-bold:var(--sy-c-bold);--yue-c-link-1:var(--sy-c-text);--yue-c-link-2:var(--sy-c-bold);--yue-c-link-border:var(--sy-c-link);--yue-c-ol-marker:var(--gray-9);--yue-c-ul-marker:var(--sage-a5);--yue-c-hr:var(--sy-c-border);--yue-c-quote:var(--sy-c-text);--yue-c-quote-border:var(--accent-a3);--yue-c-quote-symbol:var(--accent-9);--yue-c-caption:var(--sy-c-light);--yue-c-code-text:var(--accent-a11);--yue-c-code-background:var(--accent-a3);--yue-c-table-border:var(--gray-a5);--yue-c-th-background:var(--color-surface-accent);--yue-c-th-border:var(--gray-a5);--yue-c-td-border:var(--gray-a4);--yue-c-row-background:var(--sy-c-surface)}.yue{font-size:1rem;line-height:1.75;color:var(--yue-c-text)}.yue p{margin-top:1rem;margin-bottom:1.25rem}.yue a{color:var(--yue-c-link-1);font-weight:500;text-decoration:none;border-bottom:1px solid var(--yue-c-link-border)}.yue a:hover{color:var(--yue-c-link-2);border-bottom-width:2px}.yue pre a,.yue pre a:hover{border-bottom:none}.yue strong{color:var(--yue-c-bold);font-weight:600}.yue a strong,.yue blockquote strong,.yue thead th strong{color:inherit}.yue ol{margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.yue ol,.yue ol[type="1"]{list-style-type:decimal}.yue ol.upperalpha,.yue ol[type=A]{list-style-type:upper-alpha}.yue ol.loweralpha,.yue ol[type=a]{list-style-type:lower-alpha}.yue ol.upperroman,.yue ol[type=I]{list-style-type:upper-roman}.yue ol.lowerroman,.yue ol[type=i]{list-style-type:lower-roman}.yue ul{list-style-type:disc;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.yue ol>li::marker{font-weight:400;color:var(--yue-c-ol-marker)}.yue ul>li::marker{color:var(--yue-c-ul-marker)}.yue dl{margin-top:1.5rem;margin-bottom:1.5rem}.yue dt{color:var(--yue-c-bold);font-weight:600}.yue dd{margin-left:1.5rem}.yue hr{border-color:var(--yue-c-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.yue blockquote{color:var(--yue-c-quote);border-left-width:.25rem;border-left-color:var(--yue-c-quote-border);margin-top:1.2rem;margin-bottom:1.2rem;padding-left:1rem}.yue blockquote .attribution{font-size:.85em;font-style:italic}[lang=ja] .yue blockquote .attribution,[lang=ko] .yue blockquote .attribution,[lang^=zh] .yue blockquote .attribution{font-style:normal}.yue h1{color:var(--yue-c-heading);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.yue h1 strong{font-weight:900;color:inherit}.yue h2{color:var(--yue-c-heading);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.yue h2 strong{font-weight:800;color:inherit}.yue h3{color:var(--yue-c-heading);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.yue h3 strong{font-weight:700;color:inherit}.yue h4{color:var(--yue-c-heading);font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.yue h4 strong{font-weight:700;color:inherit}.yue h5,.yue h6{color:var(--yue-c-heading);font-weight:600}.yue img{display:inline;margin-top:2em;margin-bottom:2em}.yue img.rounded{border-radius:.5rem}.yue a>img,.yue figure img,.yue figure>*{margin-top:0;margin-bottom:0}.yue figcaption{color:var(--yue-c-caption);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.yue figcaption>p{margin-top:0}.yue code{color:var(--yue-c-code-text);font-weight:600;font-size:.875em}.yue a code,.yue blockquote code,.yue h1 code,.yue h2 code,.yue h3 code,.yue h4 code,.yue th code{color:inherit}.yue h2 code{font-size:.875em}.yue li>code,.yue p>code{padding:2px 4px;border-radius:3px;font-weight:500;background-color:var(--yue-c-code-background)}.yue h3 code{font-size:.9em}.yue kbd{font-family:var(--sy-f-mono)}.yue figure,.yue video{margin-top:2em;margin-bottom:2em}.yue li{margin-top:.5em;margin-bottom:.5em}.yue ol>li,.yue ul>li{padding-left:.375em}.yue ol ol,.yue ol ul,.yue ul ol,.yue ul ul{margin-top:.75em;margin-bottom:.75em}.yue h2+*,.yue h3+*,.yue h4+*,.yue hr+*{margin-top:0}.yue table{width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.86em;line-height:1.7}.yue table>caption{margin-bottom:.4rem;color:var(--yue-c-caption)}.yue thead tr{border-bottom-width:1px;border-bottom-color:var(--yue-c-th-border)}.yue thead th{color:var(--yue-c-heading);font-weight:600;vertical-align:middle}.yue tbody tr{border-bottom-width:1px;border-bottom-color:var(--yue-c-td-border)}.yue tbody tr:last-child{border-bottom-width:0}.yue tbody td{vertical-align:middle}.yue tfoot{border-top-width:1px;border-top-color:var(--yue-c-th-border)}.yue tfoot td{vertical-align:top}.yue td>p{margin:.25rem 0}.yue thead th>p{margin:0}.yue tbody td,.yue tfoot td,.yue thead th{padding:.5rem}.yue section{clear:both}.yue section>div{margin-bottom:2rem}.yue dd>p:first-child{margin-top:0}.yue p.lead{font-size:1.2rem;color:var(--sy-c-light);margin-bottom:0}.yue p.lead+hr{margin-top:1rem}.yue p.rubric{color:var(--yue-c-heading);font-weight:600;margin-top:2rem}.yue .sidebar{background-color:var(--sy-c-surface);border:1px solid var(--sy-c-border);border-radius:6px;clear:right;float:right;margin-left:1rem;margin-bottom:1rem;margin-right:0;width:30%}@media (max-width:767px){.yue .sidebar{float:none;width:100%;margin-left:0}}.yue .sidebar>*{padding-left:1rem;padding-right:1rem}.yue .sidebar img{margin-top:1rem;margin-bottom:1rem}.yue .sidebar-title{font-weight:500;border-bottom:1px solid var(--sy-c-border);margin:0;padding-top:.5rem;padding-bottom:.5rem}.yue dl.simple>dd>p,.yue ol.simple>li>p,.yue ul.simple>li>p{margin:0}.yue a.headerlink{visibility:hidden;margin-left:6px;color:var(--sy-c-light);font-weight:300;font-size:58%;font-family:var(--sy-f-mono);--icon-url:var(--lucide-link-url);-webkit-mask:var(--icon-url) no-repeat;mask:var(--icon-url) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;width:1em;height:1em;display:inline-block;vertical-align:middle;font-style:normal;background-color:currentColor}.yue .math a.headerlink,h1:hover a.headerlink,h2:hover a.headerlink,h3:hover a.headerlink,h4:hover a.headerlink,h5:hover a.headerlink,h6:hover a.headerlink{visibility:visible}.yue .toctree-wrapper a,.yue a.image-reference{border-bottom:none}.yue .toctree-wrapper p.caption{font-size:.86rem;font-weight:500;font-family:var(--sy-f-heading);color:var(--sy-c-light);text-transform:uppercase;letter-spacing:.4px;padding:.8rem 0 .4rem;border-bottom:1px solid var(--sy-c-divider)}.yue .align-left{clear:left;float:left;margin:0 1rem 1rem}.yue .align-right{clear:right;float:right;margin:0 1rem 1rem}.yue .align-center{display:block;text-align:center}.yue .align-center,.yue figure.align-center img{margin-left:auto;margin-right:auto}a.footnote-reference{font-size:.65rem;vertical-align:top}aside.footnote>span,div.citation>span{float:left;font-weight:500;padding-right:.25rem}aside.footnote>p,div.citation>p{margin-bottom:.5rem;margin-top:.5rem;margin-left:2rem}.yue kbd.kbd:not(.compound){font-size:.86rem;padding:2px 5px;border-radius:3px;margin-right:.25rem}.yue kbd.compound>kbd{margin-left:.25rem}.yue .menuselection{font-weight:500;font-size:.86rem}.light .searchbox kbd,.light .yue kbd.kbd:not(.compound){border:0;background:linear-gradient(-225deg,#e6e6e6,#f8f8f8);box-shadow:inset 0 -2px #dbdbdb,inset 0 0 1px 1px #fff,0 1px 2px 1px #50505066}.dark .searchbox kbd,.dark .yue kbd.kbd:not(.compound){border:0;background:linear-gradient(-225deg,#353434,#141414);box-shadow:inset 0 -2px #373737,inset 0 0 1px 1px #222,0 1px 2px 1px #000}.yue p.centered{text-align:center}.hlist td{vertical-align:top}.dark .dark-hidden,.dark .light-only,.light .dark-only,.light .light-hidden{display:none}.yue .genindex-jumpbox,.yue .modindex-jumpbox{border-top:1px solid var(--sy-c-border);border-bottom:1px solid var(--sy-c-border);padding:2px .4rem}.yue table.modindextable td:first-of-type{width:28px}.yue table.modindextable img.toggler{margin:0}.yue table.modindextable tr.cap{font-size:1.12rem;background:var(--sy-c-surface);font-family:var(--sy-f-mono)}.yue h2+table.indextable,.yue table.indextable ul{margin-top:0}:root{--attention-icon:var(--lucide-alert-url);--attention-1:var(--crimson-surface);--attention-2:var(--crimson-a3);--attention-3:var(--crimson-9);--attention-4:var(--crimson-a11);--caution-icon:var(--lucide-zap-url);--caution-1:var(--amber-surface);--caution-2:var(--amber-a3);--caution-3:var(--amber-9);--caution-4:var(--amber-11);--danger-icon:var(--lucide-skull-url);--danger-1:var(--ruby-surface);--danger-2:var(--ruby-a3);--danger-3:var(--ruby-9);--danger-4:var(--ruby-a11);--error-icon:var(--lucide-close-url);--error-1:var(--red-surface);--error-2:var(--red-a3);--error-3:var(--red-9);--error-4:var(--red-a11);--hint-icon:var(--lucide-bell-url);--hint-1:var(--cyan-surface);--hint-2:var(--cyan-a3);--hint-3:var(--cyan-9);--hint-4:var(--cyan-a11);--important-icon:var(--lucide-flame-url);--important-1:var(--violet-surface);--important-2:var(--violet-a3);--important-3:var(--violet-9);--important-4:var(--violet-a11);--note-icon:var(--lucide-calendar-url);--note-1:var(--blue-surface);--note-2:var(--blue-a3);--note-3:var(--blue-9);--note-4:var(--blue-a11);--tip-icon:var(--lucide-rocket-url);--tip-1:var(--green-surface);--tip-2:var(--green-a3);--tip-3:var(--green-9);--tip-4:var(--green-a11);--warning-icon:var(--lucide-zap-url);--warning-1:var(--orange-surface);--warning-2:var(--orange-a3);--warning-3:var(--orange-9);--warning-4:var(--orange-a11);--seealso-icon:var(--lucide-link-url);--seealso-1:var(--gold-surface);--seealso-2:var(--gold-a3);--seealso-3:var(--gold-9);--seealso-4:var(--gold-a11);--todo-icon:var(--lucide-bookmark-url);--todo-1:var(--bronze-surface);--todo-2:var(--bronze-a3);--todo-3:var(--bronze-9);--todo-4:var(--bronze-a11);--versionadded-1:var(--green-surface);--versionadded-2:var(--green-9);--versionchanged-1:var(--amber-surface);--versionchanged-2:var(--amber-9);--versionremoved-1:var(--red-surface);--versionremoved-2:var(--red-9);--deprecated-1:var(--red-surface);--deprecated-2:var(--red-9)}.admonition{--icon-url:var(--lucide-bell-url);--color-1:var(--color-surface-accent);--color-2:var(--accent-a3);--color-3:var(--accent-9);--color-4:var(--accent-a11);position:relative;padding:.825rem 1rem;margin-top:1rem;margin-bottom:1rem;border-left:4px solid var(--color-3);background-color:var(--color-1)}.admonition:before{position:absolute;content:"";top:6px;left:-12px;width:20px;height:20px;border-radius:100%;background-color:var(--color-3)}.admonition:after{position:absolute;content:"";top:10px;left:-8px;-webkit-mask:var(--icon-url) no-repeat;mask:var(--icon-url) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;font-style:normal;width:12px;height:12px;background-color:#fff}.admonition p.admonition-title{position:relative;margin:-.825rem -1rem .825rem -19px;padding:4px 18px;font-size:.85rem;font-weight:600;line-height:1.72;color:var(--color-4);background-color:var(--color-2);--yue-c-code:var(--color-4);--yue-c-bold:var(--color-4)}.admonition p.admonition-title svg{display:inline-block}.admonition.attention{--icon-url:var(--attention-icon);--color-1:var(--attention-1);--color-2:var(--attention-2);--color-3:var(--attention-3);--color-4:var(--attention-4)}.admonition.caution{--icon-url:var(--caution-icon);--color-1:var(--caution-1);--color-2:var(--caution-2);--color-3:var(--caution-3);--color-4:var(--caution-4)}.admonition.danger{--icon-url:var(--danger-icon);--color-1:var(--danger-1);--color-2:var(--danger-2);--color-3:var(--danger-3);--color-4:var(--danger-4)}.admonition.error{--icon-url:var(--error-icon);--color-1:var(--error-1);--color-2:var(--error-2);--color-3:var(--error-3);--color-4:var(--error-4)}.admonition.hint{--icon-url:var(--hint-icon);--color-1:var(--hint-1);--color-2:var(--hint-2);--color-3:var(--hint-3);--color-4:var(--hint-4)}.admonition.important{--icon-url:var(--important-icon);--color-1:var(--important-1);--color-2:var(--important-2);--color-3:var(--important-3);--color-4:var(--important-4)}.admonition.note{--icon-url:var(--note-icon);--color-1:var(--note-1);--color-2:var(--note-2);--color-3:var(--note-3);--color-4:var(--note-4)}.admonition.tip{--icon-url:var(--tip-icon);--color-1:var(--tip-1);--color-2:var(--tip-2);--color-3:var(--tip-3);--color-4:var(--tip-4)}.admonition.warning{--icon-url:var(--warning-icon);--color-1:var(--warning-1);--color-2:var(--warning-2);--color-3:var(--warning-3);--color-4:var(--warning-4)}.admonition.seealso{--icon-url:var(--seealso-icon);--color-1:var(--seealso-1);--color-2:var(--seealso-2);--color-3:var(--seealso-3);--color-4:var(--seealso-4)}.admonition.admonition-todo{--icon-url:var(--todo-icon);--color-1:var(--todo-1);--color-2:var(--todo-2);--color-3:var(--todo-3);--color-4:var(--todo-4)}.yue .admonition>*{margin-top:0}.yue .admonition>:last-child{margin-bottom:0!important}span.versionmodified{color:var(--sy-c-bold);font-weight:600}div.deprecated,div.versionadded,div.versionchanged,div.versionremoved{position:relative;padding:6px 1rem;margin:1rem 0;border-left:4px solid var(--color-2);background-color:var(--color-1);line-height:1.72}div.deprecated:before,div.versionadded:before,div.versionchanged:before,div.versionremoved:before{position:absolute;content:var(--version-icon);top:10px;left:-12px;color:#fff;width:20px;height:20px;border-radius:100%;background-color:var(--color-2);text-align:center;font:normal 700 14px/20px var(--sy-f-mono)}div.versionadded{--color-1:var(--versionadded-1);--color-2:var(--versionadded-2);--version-icon:"#"}div.versionchanged{--color-1:var(--versionchanged-1);--color-2:var(--versionchanged-2);--version-icon:"%"}div.versionremoved{--color-1:var(--versionremoved-1);--color-2:var(--versionremoved-2);--version-icon:"*"}div.deprecated{--color-1:var(--deprecated-1);--color-2:var(--deprecated-2);--version-icon:"!"}div.deprecated>p,div.versionadded>p,div.versionchanged>p,div.versionremoved>p{margin:0}.yue blockquote.epigraph{padding:1rem 2.4rem;border-left:0;text-align:center}.yue blockquote.highlights{border-left-width:4px;padding-top:.2rem;padding-bottom:.2rem;background-color:var(--sy-c-surface)}.yue blockquote.pull-quote{position:relative;font-size:1.24rem;padding:2.4rem 3.6rem 1.2rem;border-left:0}.yue blockquote.pull-quote:before{content:"\201c";position:absolute;top:0;left:.5rem;color:var(--yue-c-quote-symbol);font:700 4rem/1 Times New Roman,Georgia,Palatino,Times,serif}.yue blockquote.pull-quote .attribution{text-align:right}pre.literal-block{line-height:1.48;padding:1rem;font-size:.96rem;background-color:var(--syntax-pre-bg);border-radius:6px;overflow:auto}.highlight,.literal-block-wrapper{--margin:1rem;--radius:6px}.literal-block-wrapper div[class^=highlight-]{display:flex}.literal-block-wrapper .highlight{width:100%}.highlight>pre{line-height:1.48;padding:var(--margin);font-size:.96rem;font-family:var(--sy-f-mono);background-color:var(--syntax-pre-bg);border-radius:var(--radius);overflow:auto}.win .highlight>pre{font-family:"Twemoji Country Flags",var(--sy-f-mono)}.highlight .linenos{display:inline-block;box-shadow:-.05rem 0 var(--syntax-linenos-divider) inset;-webkit-user-select:none;-moz-user-select:none;user-select:none;margin-right:.8rem;padding-right:.8rem;opacity:.6}.highlight .hll{margin-left:calc(0rem - var(--margin));margin-right:calc(0rem - var(--margin));padding:0 var(--margin)}.code-block-caption{display:flex;font-size:.84rem;font-weight:600;color:var(--syntax-text);background-color:var(--syntax-cap-bg);padding:.4rem var(--margin);border-radius:var(--radius) var(--radius) 0 0}.code-block-caption+div>.highlight>pre{border-top-left-radius:0;border-top-right-radius:0}div[class^=highlight]>.highlight>pre{display:grid}.yue .table-wrapper{width:100%;overflow-x:auto;margin-top:2rem;margin-bottom:2rem;border:1px solid var(--yue-c-table-border);border-radius:6px}.yue .table-wrapper table{margin:0}.yue .table-wrapper thead tr{border-top:1px solid var(--yue-c-td-border)}.yue .table-wrapper thead tr:first-child{border-top:0}.yue .table-wrapper th{background-color:var(--yue-c-th-background);border-left:1px solid var(--yue-c-td-border);padding:.725rem 1rem}.yue .table-wrapper td{border-left:1px solid var(--yue-c-td-border);padding:.5rem 1rem}.yue .table-wrapper tr>td:first-child,.yue .table-wrapper tr>th:first-child{border-left:0}.yue .table-wrapper caption{padding:.5rem;margin:0;border-bottom:1px solid var(--yue-c-th-border)}.yue .table-wrapper tbody tr.row-odd{background-color:var(--yue-c-row-background)}.yue table.hlist td{vertical-align:top}.table-wrapper{overflow-x:auto;scrollbar-gutter:auto}.table-wrapper::-webkit-scrollbar{height:.75rem;width:.75rem}.table-wrapper::-webkit-scrollbar-thumb{border-radius:10px}.table-wrapper::-webkit-scrollbar-track{background-color:transparent}.table-wrapper:hover::-webkit-scrollbar-thumb{background-color:hsla(0,0%,61%,.2);background-clip:content-box;border:3px solid transparent}.yue table.ghost td,.yue table.ghost th{border-left:0;border-right:0;background-color:transparent}.yue table.ghost caption{margin-bottom:0;padding-bottom:.5rem;border-bottom:3px solid var(--yue-c-td-border)}.yue table.ghost thead tr:first-child{border-top:0;border-bottom-width:3px}.yue .table-wrapper.ghost{border:0}:root{--sig-property:var(--syntax-keyword);--sig-name:var(--syntax-property);--sig-typehint:var(--syntax-constant);--sig-param:var(--syntax-meta)}dt.sig{position:relative;font-size:.92rem;padding:.25rem .5rem .25rem 3rem;text-indent:-2.4rem;border-radius:6px}dt.sig:after{content:"";display:table;clear:both}dt.sig:hover{background:var(--sy-c-surface)}dt.sig+dd{font-size:.92rem;margin-left:2rem}dt.sig>em.property:first-child{color:var(--sig-property)}dl.field-list a{font-weight:400}dt.sig+dd>div{margin-bottom:1rem}dt.sig+dd>dl.field-list>dt{text-transform:uppercase;font-size:.76rem}em.property,em.sig-param{font-style:normal}em.sig-param{color:var(--sy-c-light)}span.sig-name,span.sig-prename{color:var(--sig-name)}span.sig-name{font-weight:600}span.sig-return-icon{color:var(--sy-c-light)}span.sig-return-typehint,span.sig-return-typehint>a{color:var(--sig-typehint)}span.pre,span.sig-paren{font-family:var(--sy-f-mono)}dt.sig>a.internal{font-size:.82rem;border:0;color:var(--sy-c-light)}dt.sig>a.internal:before{content:"\a";white-space:pre}.viewcode-block{position:relative}.viewcode-back{position:absolute;top:-1.5rem;font-size:.8rem}.classifier{font-style:oblique;font-weight:400}.classifier:before{font-style:normal;margin-left:.1rem;margin-right:.5rem;content:":";display:inline-block}.yue .table-wrapper.autosummary{border-left:0;border-right:0;border-radius:0}.yue .table-wrapper table.autosummary td{border:none;padding-top:.25rem;padding-bottom:.25rem}.yue p.rubric+div.autosummary{margin-top:0}.hamburger{position:relative;display:inline-block;width:16px;height:14px;overflow:hidden;cursor:pointer}.hamburger>span{position:absolute;width:16px;height:2px;left:0;background-color:var(--sy-c-text);transition:top .25s,transform .25s}.hamburger_1{top:0}.hamburger_2{top:6px}.hamburger_3{top:12px}button[aria-expanded=true] .hamburger .hamburger_1{top:6px;transform:translate(0) rotate(225deg)}button[aria-expanded=true] .hamburger .hamburger_2{top:6px;transform:translate(18px)}button[aria-expanded=true] .hamburger .hamburger_3{top:6px;transform:translate(0) rotate(135deg)}.searchbox{position:relative}.searchbox input{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;padding:6px 12px;font-size:.92rem;font-family:var(--sy-f-text);border-radius:6px;outline:0;background:var(--sy-c-surface)}.searchbox button,.searchbox kbd{position:absolute;font-size:.68rem;font-weight:600;font-family:var(--sy-f-mono);padding:2px 6px;margin:6px;right:0;border-radius:3px;border:1px solid var(--sy-c-border);background-color:var(--sy-c-background);opacity:1;transition:opacity .2s ease}.searchbox input:focus+kbd{opacity:0}.searchform{display:flex;position:relative;align-items:center}.searchform input[name=q]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;padding:6px 12px;font-size:.92rem;font-family:var(--sy-f-text);border-radius:6px;outline:0;background:var(--sy-c-surface)}.searchform input[name=q]+button{position:absolute;font-size:.68rem;font-weight:600;font-family:var(--sy-f-text);padding:2px 6px;margin:6px;right:0;border-radius:3px;border:1px solid var(--sy-c-divider);background-color:var(--sy-c-background);opacity:1;transition:opacity .2s ease}.search .highlighted{background-color:var(--accent-a4)}#search-results{border-top:1px solid var(--sy-c-border)}#search-results h2{margin-top:2rem;margin-bottom:.725rem}#search-results .search-summary{font-weight:500;color:var(--sy-c-light)}#search-results ul.search{list-style-type:none;margin-left:0;padding-left:0;padding-top:.625rem;padding-bottom:2rem}#search-results ul.search>li{padding-left:0}#search-results ul.search>li+li{padding-top:1rem;border-top:1px solid var(--sy-c-divider)}#search-results ul.search li>a{font-weight:600}#search-results ul.search p.context{margin-top:.5rem;font-size:.875rem}.demo{border:1px solid var(--sy-c-border);border-radius:6px}.demo-code .highlight>pre{border-bottom-left-radius:0;border-bottom-right-radius:0}.demo-result{padding:1rem}.container.image-1,.container.video-1{border:.5rem solid var(--accent-a3);border-radius:6px}.container.image-1>img{border-radius:4px;margin:0}.container.video-1 iframe,.container.video-1 video{border-radius:4px;width:100%;margin:0}.container.image-2,.container.video-2{border:1px solid var(--sy-c-border);padding:1rem;border-radius:6px}.container.image-2>img,.container.video-2>video{margin:0}.container.buttons{margin:2rem 0 4.2rem}.container.buttons>p{display:flex;gap:1rem}.container.buttons a{display:inline-block;padding:0 2rem;line-height:2.6rem;border-radius:2.6rem;border:2px solid var(--sy-c-border);font-weight:600;background-color:var(--sy-c-surface);transition:all .2s ease}.container.buttons a:first-child{color:var(--accent-9-contrast);background-color:var(--accent-9);border-color:var(--accent-9)}.container.buttons a:hover{color:var(--sy-c-bold);border-color:var(--accent-9);background-color:var(--sy-c-background)}#ethical-ad-placement .ethical-sidebar{position:relative;background-color:var(--sy-c-surface);border:none;padding:.8rem}#ethical-ad-placement .ethical-text a{color:var(--sy-c-text)!important}#ethical-ad-placement .ethical-text a:hover{color:var(--sy-c-link-hover)!important}.sy-main #ethical-ad-placement .ethical-sidebar{margin-left:0;max-width:380px}.sy-main #ethical-ad-placement .ethical-image-link{flex-shrink:0;margin-right:.4rem}.sy-main #ethical-ad-placement .ethical-content{display:flex}.sy-main #ethical-ad-placement .ethical-text{margin-top:0}.sy-main #ethical-ad-placement .ethical-callout{position:absolute;right:.4rem;bottom:.4rem}#carbonads{margin:1rem 0;position:relative;display:block;background-color:var(--sy-c-surface);border:none;border-radius:8px;padding:.8rem .8rem 1.6rem}#carbonads a{border:0;font-weight:400}#carbonads img{margin:0}.carbon-wrap{display:flex;align-items:center;justify-content:space-between;flex-direction:column}.carbon-text{display:block;margin:.5rem 0;line-height:1.42;font-size:.78rem;text-align:center}.carbon-text:hover{color:var(--sy-c-link-hover)}.carbon-poweredby{position:absolute;opacity:.68;right:.8rem;bottom:.5rem;font-size:.68rem;text-transform:uppercase}.carbon-poweredby:hover{text-decoration:underline}.sy-main #carbonads{max-width:380px;padding:1rem;margin-top:1.6rem}.sy-main .carbon-wrap{flex-direction:row;align-items:flex-start}.sy-main .carbon-text{text-align:left;margin-top:0;margin-left:1rem;font-size:.86rem}.bsa{margin:1rem 0}.bsa a{text-decoration:none;border-bottom:none}.bsa-container{display:flex;flex-flow:row nowrap;align-items:center;justify-content:space-between;padding:15px 20px;border-radius:6px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.1);text-decoration:none}.bsa-ad-via{text-align:right}.bsa-ad-via a{font-weight:300;font-size:10px;border:none;background:var(--gray-a3);padding:3px 10px;border-radius:2px}.bsa-main{display:flex;flex-grow:1;flex-flow:row nowrap;align-items:center;justify-content:center;margin:0 auto}.bsa-img{max-height:40px;margin-right:20px;line-height:0}.yue a.bsa-container .bsa-img{margin-top:0;margin-bottom:0}.bsa-details{display:flex;flex-flow:column nowrap;margin-right:20px}.bsa-tagline{margin-bottom:3px;font-weight:600;font-size:9px;line-height:1;letter-spacing:1.5px;text-transform:uppercase}.bsa-desc{max-width:600px;font-weight:400;font-size:12px;line-height:1.4;letter-spacing:1px}.bsa-cta{padding:10px 16px;transform:translateY(-1px);border-radius:3px;font-weight:600;font-size:10px;line-height:1;letter-spacing:1px;text-transform:uppercase;white-space:nowrap;transition:all .3s ease-in-out}@media (max-width:940px){.bsa-details{font-size:14px;margin-right:0}.bsa-cta{display:none}}@media (min-width:768px) and (max-width:820px){.bsa-img{display:none}}@media (max-width:480px){.bsa-img{display:none}}.repo-stats{margin-bottom:1rem;padding:.5rem;border:1px solid var(--sy-c-divider);border-radius:6px}.repo-stats:hover{background-color:var(--sy-c-surface)}.repo-stats-count{color:var(--sy-c-light)}.repo-stats strong{font-weight:500;font-family:var(--sy-f-mono);color:inherit}.edit-this-page{border-top:1px solid var(--sy-c-divider);margin:1rem 0;padding:.5rem 0;font-size:.8rem;font-weight:600}.repo-stats+.edit-this-page{border-top:0;margin-top:0;padding-top:0}.edit-this-page a{color:var(--sy-c-text)}.edit-this-page a:hover{color:var(--sy-c-link-hover)}.edit-this-page a:after{content:" →"}.back-to-top{display:none;align-items:center;gap:.25rem;position:fixed;z-index:10;bottom:68px;left:50%;transform:translateX(-50%);background:var(--sy-c-background);border-radius:2rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 1px 0 hsla(220,9%,46%,.5);font-size:.8rem;padding:.4rem .8rem .4rem .6rem}.dark .back-to-top{background:var(--slate-2);box-shadow:0 .2rem .5rem hsla(0,0%,100%,.05),0 0 1px 0 rgba(154,164,184,.5)}.back-to-top:hover{color:var(--accent-9-contrast);background:var(--accent-9)}.back-to-top svg{height:1rem;width:1rem;fill:currentColor;display:inline-block}.back-to-top[data-visible=true]{display:flex}.icon-link,.icon-link span{display:inline-block}.icon-link span{vertical-align:middle}.icon-link .icon{padding:.1rem;border-radius:6px;border:1px solid var(--sy-c-border);margin-right:.4rem;opacity:.8}.icon-link svg{width:1.5rem;height:1.5rem}.icon-link:hover .icon{opacity:1}.announcement{position:sticky;top:0;left:0;width:100%;padding:.8rem 2rem;display:flex;align-items:center;color:var(--sy-c-banner,var(--accent-9-contrast));background-color:var(--sy-c-banner-bg,var(--accent-a11));z-index:20}.announcement a{text-decoration:underline}.announcement ::-moz-selection{color:var(--sy-c-banner,var(--accent-9-contrast))}.announcement ::selection{color:var(--sy-c-banner,var(--accent-9-contrast))}.announcement-inner{width:100%}.announcement-close{position:absolute;top:.8rem;right:1rem}.sy-head{position:sticky;top:var(--sy-s-banner-height);height:var(--sy-s-navbar-height);background-color:transparent;z-index:20}.sy-head-blur{position:absolute;top:0;left:0;width:100%;height:100%;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);background-color:var(--sand-a1);box-shadow:0 0 var(--sy-c-background-contrast),0 2px 4px var(--gray-a1),0 1px 0 var(--sy-c-divider);z-index:-1}.sy-head-inner{display:flex;padding-left:max(env(safe-area-inset-right),1.5rem);padding-right:max(env(safe-area-inset-right),1rem);justify-content:space-between;align-items:center;height:var(--sy-s-navbar-height)}.sy-head-brand img{height:28px}.sy-head-brand .dark-logo,.sy-head-brand img+strong{display:none}.dark .sy-head-brand .dark-logo{display:inline}.dark .sy-head-brand .light-logo{display:none}.light .sy-head-brand .light-logo{display:inline}.light .sy-head-brand .dark-logo{display:none}.sy-head-links a,.sy-head-links button{padding:.5rem;font-size:.95rem;font-weight:500;white-space:nowrap}.sy-head-links .link i.external-link{font-size:68%;opacity:.6;color:var(--sy-c-light);margin-left:2px}.sy-head-links .link>ul a{display:block;white-space:normal}.sy-head-links .link>ul a:hover{background:var(--sy-c-surface);border-radius:6px}.sy-head-links .link>ul small{display:block;color:var(--sy-c-light);font-weight:400}.sy-head-socials,.sy-head-socials a{display:flex;align-items:center}.sy-head-socials a{padding:.5rem}.sy-head-actions button{height:var(--sy-s-navbar-height);padding:0 .5rem}@media (max-width:767px){body[data-expanded-headnav=true]{overflow:hidden}.sy-head-nav{display:none;position:fixed;top:var(--sy-s-offset-top);bottom:0;left:0;right:0;padding:4rem 1.8rem 0;width:100%;box-sizing:border-box;border-top:1px solid var(--sy-c-divider);background-color:var(--sy-c-background);overflow-y:auto}.sy-head-nav._expanded{display:block}.sy-head-links{margin-left:auto;margin-right:auto}.sy-head-links .link{margin:.5rem 0}.sy-head-links .link i.chevron{display:none}.sy-head-links .link>ul{margin:.5rem 0 .5rem 1rem}.sy-head-extra form.searchbox{position:absolute;top:1rem;left:1.8rem;right:1.8rem}.sy-head-extra{flex-direction:column;padding:2rem 0 1rem;width:100%}}@media (min-width:768px){.sy-head-inner{padding-right:max(env(safe-area-inset-right),1.5rem)}.sy-head-nav{display:flex;flex-grow:1;align-items:center;justify-content:space-between}.sy-head-links[data-align=right]{--head-links-justify-content:flex-end}.sy-head-links[data-align=center]{--head-links-justify-content:center}.sy-head-links{display:flex;flex-grow:1;white-space:nowrap;overflow:auto;padding:0 1rem;justify-content:var(--head-links-justify-content,flex-start)}.sy-head-links .link{display:inline-flex;align-items:center;height:var(--sy-s-navbar-height)}.sy-head-links a:hover{color:var(--sy-c-link-hover)}.sy-head-links .link:hover>a{background-color:var(--sy-c-surface);border-radius:6px}.sy-head-links .link i.chevron-down{color:var(--sy-c-light)}.sy-head-links .link>ul{position:absolute;height:0;visibility:hidden;background-color:var(--sy-c-background);top:var(--sy-s-navbar-height);margin-top:-10px;padding:15px;border-radius:6px;border:1px solid var(--sy-c-divider);box-shadow:var(--sy-dropdown-shadow);z-index:9;max-width:320px}.sy-head-links .link:hover>ul{height:auto;visibility:visible}.sy-head-links .link>ul>li{padding:.2rem 0}.sy-head-socials{margin-left:.5rem}}.sy-foot{border-top:1px solid var(--sy-c-foot-divider);padding-top:1.5rem;padding-bottom:1rem;color:var(--sy-c-foot-text);background-color:var(--sy-c-foot-background)}.sy-foot-inner{padding-left:max(env(safe-area-inset-right),1.5rem);padding-right:max(env(safe-area-inset-right),1.5rem)}.sy-foot-copyright{font-size:.84rem}.sy-foot-copyright a{font-weight:500}.sy-foot-copyright a:hover{text-decoration:underline}.sy-foot-socials a{font-size:1.4rem;color:var(--sy-c-foot-text)}.sy-foot-socials a+a{margin-left:.5rem}.sy-foot-socials a svg{display:inline-block;width:1.4rem;height:1.4rem}.sy-lside .sidebar-links{margin-bottom:2rem}@media (min-width:768px){.sy-lside .sy-lside-inner{top:var(--sy-s-offset-top)}.sy-lside .sy-scrollbar{max-height:calc(100vh - var(--sy-s-offset-top));overflow-x:hidden}}.yue *{scroll-margin-top:calc(var(--sy-s-offset-top) + 68px)}.sy-content{max-width:64rem;min-height:calc(100vh - var(--sy-s-offset-top) - 80px)}@media (max-width:767px){#lside{position:fixed;z-index:18;top:var(--sy-s-offset-top);left:0;bottom:0;width:300px;max-width:100%;height:calc(100vh - var(---sy-s-offset-top));overflow:auto;background:var(--sy-c-background);transform:translateX(-100%);transition:transform .2s ease}#lside._expanded{transform:translateX(0)}.lside-overlay{position:fixed;top:var(--sy-s-offset-top);left:0;width:0;height:0;background-color:var(--sy-c-overlay);opacity:0;transition:width 0 .25s,height 0 .25s,opacity .25s}#lside._expanded+.lside-overlay{width:100%;height:100%;opacity:1;z-index:16}}@media (max-width:1279px){.sy-rside{position:fixed;z-index:25;top:0;right:0;bottom:0;width:20rem;max-width:100%;padding-top:2rem;padding-bottom:1rem;overflow:auto;background:var(--sy-c-background);transform:translateX(110%);transition:transform .2s ease;box-shadow:0 0 var(--sy-c-background-contrast),-12px 0 16px var(--gray-a1)}#rside._expanded{transform:translateX(0)}.rside-close{position:absolute;top:16px;right:16px;width:2rem;height:2rem;display:flex;align-items:center;justify-content:center;font-size:1.4rem}.rside-overlay{position:fixed;top:0;left:0;width:0;height:0;background-color:var(--sy-c-overlay);opacity:0;transition:width 0 .25s,height 0 .25s,opacity .25s}#rside._expanded+.rside-overlay{width:100%;height:100%;opacity:1;z-index:22}}@media (min-width:768px){.sy-main{width:calc(100% - 18rem);max-width:52rem}}@media (min-width:1280px){.sy-main{width:calc(100% - 34rem);max-width:none}.sy-rside .sy-scrollbar{max-height:calc(100vh - var(--sy-s-offset-top) - env(safe-area-inset-bottom))}.yue *{scroll-margin-top:calc(var(--sy-s-offset-top) + 24px)}}.nav-languages button,.nav-versions button{cursor:pointer;padding:0;margin:0;background:transparent;border:0;white-space:nowrap}.nav-versions .chevron-down{color:var(--sy-c-light)}@media (max-width:767px){.nav-languages,.nav-versions{width:100%;background-color:var(--sy-c-surface);border-radius:6px;padding-bottom:.6rem;margin-bottom:1rem}.nav-languages button,.nav-versions button{padding:.5rem 1rem;font-weight:500;font-size:.76rem;color:var(--sy-c-light)}.nav-languages button>i,.nav-versions button>i{display:none}.nav-versions ul{padding:0 .6rem}.nav-versions li{padding:.2rem .4rem;display:inline-block}.nav-languages li{font-size:.94rem;padding:.32rem 1rem}}@media (min-width:768px){.nav-languages,.nav-versions{display:flex;align-items:center;position:relative;width:auto;height:var(--sy-s-navbar-height);background:transparent;color:var(--sy-c-text)}.nav-languages button,.nav-versions button{padding:0 .5rem;border-right:1px solid var(--gray-3)}.nav-languages-choices,.nav-versions-choices{position:absolute;visibility:hidden;top:3rem;right:-.6rem;min-width:120px;max-height:60vh;box-sizing:border-box;background-color:var(--sy-c-background);border-radius:6px;overflow-x:hidden;overflow-y:auto;padding:.8rem 1rem;box-shadow:var(--sy-dropdown-shadow)}.nav-languages:hover .nav-languages-choices,.nav-versions:hover .nav-versions-choices{visibility:visible}.nav-languages li,.nav-versions li{padding:.1rem 0}.nav-languages a,.nav-versions a{display:block;padding:.2rem .6rem;color:var(--sy-c-text);white-space:nowrap}.nav-languages a:hover,.nav-versions a:hover{color:var(--sy-c-link-hover);background:var(--sy-c-surface);border-radius:6px}}.sy-breadcrumbs{position:sticky;top:var(--sy-s-offset-top);background-color:var(--sy-c-background);padding:0 1.5rem;z-index:5}.sy-breadcrumbs-inner{padding:.8rem 0;border-bottom:1px solid var(--sy-c-divider)}.sy-breadcrumbs ol{display:flex;font-size:.94rem;white-space:nowrap;overflow:auto}.sy-breadcrumbs button{display:flex;align-items:center}.sy-breadcrumbs ol a{color:var(--sy-c-light)}.sy-breadcrumbs ol a:hover{color:var(--sy-c-bold)}.sy-breadcrumbs ol a+span{padding:0 .4rem;font-weight:300;color:var(--sy-c-light)}@media (min-width:1280px){.sy-breadcrumbs{display:none}}@media (min-width:768px){.sy-breadcrumbs-inner{padding:1.5rem 0 1rem}}.globaltoc{padding-bottom:20px}.globaltoc .caption{font-size:.86rem;font-weight:500;font-family:var(--sy-f-heading);color:var(--sy-c-light);text-transform:uppercase;letter-spacing:.4px;padding:.8rem 0 .4rem;border-top:1px solid var(--sy-c-divider)}.globaltoc>p.caption:first-of-type{padding-top:0;border-top:none}.globaltoc .caption+ul{margin-bottom:1.5rem}.globaltoc ul+.caption{margin-top:2.5rem}.globaltoc li{margin:.6rem 0}.globaltoc li>ul{margin-left:.6rem;font-size:.96rem}.globaltoc li.toctree-l1>ul{margin-left:.2rem;border-left:1px solid var(--gray-3)}.globaltoc li.toctree-l2{padding-left:.9rem;margin-left:-1px;border-left:1px solid transparent}.globaltoc li.toctree-l2.current{border-color:var(--sy-c-link)}.globaltoc>ul a.current{font-weight:500;color:var(--sy-c-link)}.globaltoc>ul a:hover{color:var(--sy-c-link-hover)}.globaltoc a.external:after{content:"";-webkit-mask:var(--lucide-external-link-url) no-repeat;mask:var(--lucide-external-link-url) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;width:.825rem;height:.825rem;margin-left:.2rem;display:inline-block;vertical-align:middle;font-style:normal;background-color:var(--sy-c-light)}.globaltoc li{position:relative}.globaltoc li>button{position:absolute;top:.2rem;right:0;display:flex;justify-content:center;align-items:center;width:1.2rem;height:1.2rem;border-radius:3px}.globaltoc li>button:hover{background-color:var(--sy-c-surface)}.globaltoc li._expand>ul,.globaltoc li.current>ul{display:block}.globaltoc li._collapse>ul,.globaltoc li>ul{display:none}.globaltoc li>button>i{transform:rotate(0deg);transition:transform .2s ease}.globaltoc li._expand>button>i,.globaltoc li.current>button>i{transform:rotate(90deg)}.globaltoc li._collapse>button>i{transform:rotate(0deg)}.sy-deprecated{padding:.8rem;font-size:.85rem;background-color:rgba(255,221,0,.1);border-radius:6px}.sy-deprecated a{color:var(--sy-c-link);text-decoration:underline}.sy-deprecated a:hover{color:var(--sy-c-link-hover)}.sy-rside-inner>div{margin-bottom:1rem}.sy-rside-inner>div>h3{font-size:.8rem;font-weight:500;letter-spacing:.4px;text-transform:uppercase;margin-bottom:1rem}html[lang=ja] .sy-rside-inner>div>h3,html[lang=ko] .sy-rside-inner>div>h3,html[lang=zh-TW] .sy-rside-inner>div>h3,html[lang=zh] .sy-rside-inner>div>h3{letter-spacing:0;font-size:.86rem;font-weight:600}.localtoc>ul li{margin-top:.36rem;margin-bottom:.36rem}.localtoc>ul li>a:hover{color:var(--sy-c-link-hover)}.localtoc>ul li.active>a{font-weight:500;color:var(--sy-c-link)}.localtoc>ul>li ul{padding-left:.8rem}.sy-rside ul.this-page-menu{margin-top:-.6rem}.sy-rside ul.this-page-menu a{font-size:.96rem}.sy-rside ul.this-page-menu a:hover{color:var(--sy-c-link-hover)}.navigation{gap:2rem;margin-top:2rem;padding-top:1rem;border-top:1px solid var(--sy-c-divider)}.navigation>div{width:100%}.navigation a{display:inline-flex;align-items:center}.navigation a:hover{color:var(--sy-c-link-hover)}.navigation-next{text-align:right}.navigation-next a{justify-content:end}.navigation .page-info{padding:0 8px}.navigation .page-info>span{font-size:.8rem;color:var(--sy-c-light)}:root{--readthedocs-search-font-family:var(--sy-f-text);--readthedocs-search-color:var(--sy-c-text);--readthedocs-search-input-background-color:var(--gray-3);--readthedocs-search-content-border-color:var(--gray-4);--readthedocs-search-content-background-color:var(--sy-c-background);--readthedocs-search-result-section-color:var(--sy-c-text);--readthedocs-search-result-section-subheading-color:var(--sy-c-heading);--readthedocs-search-result-section-highlight-color:var(--accent-9);--readthedocs-search-result-section-border-color:var(--sy-c-border)}.yue button.copybtn{align-items:center;justify-content:center;background-color:transparent;border:none;color:var(--syntax-text)}.yue button.copybtn>svg{width:1.4rem;height:1.4rem}.yue button.copybtn:hover{color:var(--syntax-meta)}.yue .highlight button.copybtn:hover,.yue button.copybtn:after{background-color:transparent}.yue button.copybtn:after{color:var(--syntax-text)}.yue button.copybtn.success{border-color:var(--green-a10);color:var(--green-a10)}.yue button.copybtn.success:after{color:var(--green-a10)}.yue{--sd-color-primary:var(--accent-a11);--sd-color-secondary:var(--gold-a11);--sd-color-success:var(--green-a11);--sd-color-info:var(--blue-a11);--sd-color-warning:var(--orange-a11);--sd-color-danger:var(--red-a11);--sd-color-light:var(--sand-a2);--sd-color-muted:var(--gray-8);--sd-color-dark:#212122;--sd-color-black:#000;--sd-color-white:#fff;--sd-color-primary-highlight:var(--accent-a8);--sd-color-secondary-highlight:var(--gold-a8);--sd-color-success-highlight:var(--green-a8);--sd-color-info-highlight:var(--blue-a8);--sd-color-warning-highlight:var(--orange-a8);--sd-color-danger-highlight:var(--red-a8);--sd-color-light-highlight:var(--gray-4);--sd-color-muted-highlight:var(--gray-11);--sd-color-dark-highlight:#121211;--sd-color-black-highlight:#000;--sd-color-white-highlight:#d9d9d9;--sd-color-primary-text:var(--accent-9-contrast);--sd-color-secondary-text:var(--gold-9-contrast);--sd-color-success-text:var(--green-9-contrast);--sd-color-info-text:var(--blue-9-contrast);--sd-color-warning-text:var(--orange-9-contrast);--sd-color-danger-text:var(--red-9-contrast);--sd-color-light-text:var(--sy-c-text);--sd-color-muted-text:#fff;--sd-color-dark-text:#fff;--sd-color-black-text:#fff;--sd-color-white-text:#212529;--sd-color-shadow:var(--gray-1);--sd-color-card-border:var(--sy-c-border);--sd-color-card-border-hover:var(--accent-a9);--sd-color-tabs-label-inactive:var(--sy-c-bold);--sd-color-tabs-label-active:var(--sd-color-primary);--sd-color-tabs-underline-active:var(--sd-color-primary);--sd-color-tabs-label-hover:var(--accent-9);--sd-color-tabs-underline-hover:var(--accent-9)}.yue .surface{--sd-color-card-text:var(--sy-c-light);--sd-color-card-border:transparent;--sd-color-card-background:var(--sy-c-surface)}.yue a.sd-badge,.yue a.sd-badge:hover{border-bottom:0}.yue .sd-badge{font-weight:600;border-radius:3px}.yue .sd-btn{border-color:var(--sy-c-border)}.yue .sd-tab-set>label{padding:1rem .25rem .5rem;font-size:.84rem;font-weight:500}.yue .sd-tab-set>label~label{margin-left:1rem}.yue .sd-tab-content{padding:0;box-shadow:0 -.0625rem var(--sy-c-divider)}.yue .sd-tab-content .code-block-caption,.yue .sd-tab-content .highlight pre{border-radius:0}.yue .sd-card-title{color:var(--sy-c-text)}.yue .sd-card-title>svg{position:relative;top:-1px;margin-right:.25rem}.yue .sd-card-hover:hover{transform:scale(1)}.yue .sd-card-hover:hover .sd-card-title{color:var(--sy-c-link-hover)}.yue .sd-card a,.yue .sd-card a:hover{border-bottom:0}.yue .surface .sd-card-body,.yue .surface .sd-card-footer,.yue .surface .sd-card-header{padding-left:1.5rem;padding-right:1.5rem}.yue .surface .sd-card-footer,.yue .surface .sd-card-header{border-color:var(--sy-c-border)}@media (print){.yue .sd-card{page-break-inside:avoid}}.yue a.sd-text-wrap:hover{border-bottom-width:1px}.sphinx-tabs [role=tablist]{border-color:var(--sy-c-divider)}.yue .sphinx-tabs-tab{color:var(--sy-c-text);line-height:inherit;padding:1rem .25rem .5rem;font-size:.84rem;font-weight:500;border:none;border-bottom:.125rem solid transparent}.yue .sphinx-tabs-tab:hover{color:var(--sd-color-tabs-label-hover);border-color:var(--sd-color-tabs-underline-hover)}.yue .sphinx-tabs-tab[aria-selected=true]{border:none;border-bottom:.125rem solid var(--sd-color-tabs-underline-active);color:var(--sd-color-tabs-label-active);background-color:transparent}.yue .sphinx-tabs-tab+.sphinx-tabs-tab{margin-left:1rem}.yue .sphinx-tabs-panel{border:none;padding:0;margin:0;border-radius:0;background-color:transparent}.yue .sphinx-tabs-panel.code-tab{padding:0}.yue .sphinx-tabs-panel.code-tab .code-block-caption,.yue .sphinx-tabs-panel.code-tab .highlight pre{border-radius:0}.yue{--jp-widgets-input-border-color:var(--gray-5);--jp-widgets-input-focus-border-color:var(--gray-8);--jp-widgets-slider-active-handle-color:var(--gray-4);--jp-widgets-slider-handle-border-color:var(--sy-c-border)}.yue .jupyter_container{background-color:var(--sy-c-background);border:3px solid var(--sy-c-border);border-radius:6px;overflow:hidden;box-shadow:none}.sy-main .yue .jupyter_container div[class^=highlight]{padding:0}.yue .jupyter_container div.highlight{background-color:var(--syntax-pre-bg)}.yue .jupyter_container div.cell_input{background-color:var(--syntax-pre-bg);border:0;border-radius:0}.yue .jupyter_container div.code_cell pre{padding:0}.jupyter_container div.cell_output .output,.jupyter_container div.cell_output .stderr,.jupyter_container div.cell_output .widget-subarea{padding:.5rem}.jupyter_container div.cell_output .stderr .stderr{padding:0}.jupyter-widget-hslider .slider-container,.widget-hslider .slider-container{display:flex;align-items:center}.jupyter-widget-slider .noUi-target,.widget-slider .noUi-target{width:100%}.jupyter_container div.code_cell .highlight>pre{padding:1rem}.jupyter_container div.code_cell .highlight .hll{margin-left:-1rem;margin-right:-1rem;padding:0 1rem}.jupyter_container div.code_cell .highlight .linenos{margin-right:.8rem}.yue .jupyter_container .stderr{color:var(--red-a11);background-color:var(--red-a3)}.yue .jupyter_container .stderr .stderr{background-color:transparent}.nbinput .highlight{--radius:1px}.yue div.nblast.container{padding-top:5px}.yue div.nbinput.container div.input_area{border-color:var(--sy-c-border)}.yue div.nboutput.container div.output_area.stderr{color:var(--red-a11);background-color:var(--red-a3)}.yue div.nboutput.container div.output_area>.math-wrapper>div.math{padding-top:0}.yue .jp-RenderedHTMLCommon thead,.yue div.rendered_html thead{border-color:var(--sy-c-border)}.yue .jp-RenderedHTMLCommon tbody tr,.yue div.rendered_html tbody tr{color:var(--sy-c-text)}.yue .jp-RenderedHTMLCommon tbody tr:nth-child(odd),.yue div.rendered_html tbody tr:nth-child(odd){background-color:var(--sy-c-surface)}.yue .jp-RenderedHTMLCommon tbody tr:hover,.yue div.rendered_html tbody tr:hover{background-color:var(--color-surface-accent)}.yue{--sg-text-color:var(--sy-c-text);--sg-background-color:var(--sy-c-background);--sg-code-background-color:var(--syntax-pre-bg);--sg-tr-hover-color:var(--accent-a3);--sg-tr-odd-color:var(--sy-c-surface);--sg-tooltip-foreground:var(--sy-c-background-contrast);--sg-tooltip-background:var(--sy-c-background);--sg-tooltip-border:var(--gray-7) transparent;--sg-thumb-box-shadow-color:var(--gray-a4);--sg-thumb-hover-border:var(--accent-a9);--sg-script-out:var(--sy-c-light);--sg-script-pre:var(--syntax-pre-bg);--sg-pytb-foreground:var(--syntax-text);--sg-pytb-background:var(--red-a2);--sg-pytb-border-color:var(--red-a8);--sg-download-a-background-color:var(--accent-a3);--sg-download-a-background-image:none;--sg-download-a-border-color:1px solid var(--accent-a3);--sg-download-a-color:var(--accent-a11);--sg-download-a-hover-background-color:var(--accent-a4);--sg-download-a-hover-box-shadow-1:transparent;--sg-download-a-hover-box-shadow-2:transparent}.yue .sphx-glr-download a,.yue .sphx-glr-download a:hover,.yue .sphx-glr-thumbnails a{border-bottom:0}.yue p.sphx-glr-signature a{border-radius:0;border-bottom:0;text-decoration:underline}.yue p.sphx-glr-signature a:hover{color:var(--sy-c-link-hover)}.yue .sphx-glr-footer img{display:inline;margin:0}html.dark,html.light{--docsearch-primary-color:var(--accent-9);--docsearch-text-color:var(--sy-c-text);--docsearch-modal-background:var(--sy-c-background);--docsearch-footer-background:var(--sy-c-surface);--docsearch-searchbox-background:var(--sy-c-surface);--docsearch-searchbox-focus-background:var(--sy-c-background);--docsearch-muted-color:var(--sy-c-light);--docsearch-hit-color:var(--sy-c-text);--docsearch-hit-background:var(--sy-c-surface);--docsearch-hit-active-color:var(--accent-9-contrast);--docsearch-hit-shadow:inset 0 0 1px 0 var(--gray-a11);--docsearch-container-background:var(--sy-c-overlay)}html.light{--docsearch-key-gradient:linear-gradient(-225deg,#e6e6e6,#f8f8f8);--docsearch-key-shadow:inset 0 -2px #dbdbdb,inset 0 0 1px 1px #fff,0 1px 2px 1px #50505066}html.dark{--docsearch-key-gradient:linear-gradient(-225deg,#353434,#141414);--docsearch-key-shadow:inset 0 -2px #373737,inset 0 0 1px 1px #222,0 1px 2px 1px #000;--docsearch-footer-shadow:0 -1px 0 0 #373737,0 -3px 6px 0 #141414;--docsearch-modal-shadow:inset 1px 1px 0 0 #373737,0 3px 8px 0 #141414}#docsearch .DocSearch-Button{border-radius:6px}#docsearch .DocSearch-Button-Key,#docsearch .DocSearch-Button-Placeholder{font-size:.825rem}#docsearch .DocSearch-Button-Keys,#docsearch .DocSearch-Button-Placeholder{display:flex!important}#docsearch .DocSearch-Search-Icon{width:.875rem!important;height:.875rem!important}@media (max-width:767px){#docsearch{position:absolute;top:1rem;left:1.8rem;right:1.8rem}#docsearch .DocSearch-Button{margin-left:0;width:100%}}dl.sqla dt{color:var(--sig-name);margin-bottom:.5rem}dl.sqla dt>em{font-weight:400;font-style:normal;color:var(--sig-param)}dl.sqla dd>p.rubric{margin-top:1.5rem;text-transform:uppercase;font-size:.76rem}dl.sqla dd>p.rubric+.table-wrapper{margin-top:.75rem;border-left:0;border-right:0;border-radius:0}dl.sqla p.rubric+.table-wrapper td,dl.sqla p.rubric+.table-wrapper th{border-left:0;border-right:0;background-color:transparent}dl.sqla p.rubric+.table-wrapper td>p{margin:0}dl.sqla p.rubric+.table-wrapper tr.row-odd{background-color:transparent}dl.sqla p.rubric+.table-wrapper tr.row-even{background-color:var(--yue-c-row-background)}.yue details.toggle-details{background-color:var(--slate-a2);padding:0 1rem;border-radius:.2em}.yue details.toggle-details summary{border-left-color:var(--accent-a9);background-color:var(--gray-a2);margin-left:-1rem;margin-right:-1rem}.yue details.toggle-details[open] summary{border-radius:.2em .2em 0 0}.yue .toggle-details__container{margin-top:0;margin-bottom:0;padding-top:1rem;padding-bottom:1rem}.yue .toggle-details__container :first-child{margin-top:0}.yue .toggle-details__container :last-child{margin-bottom:0}.yue .admonition.toggle-hidden .admonition-title~*{margin-bottom:0!important}.yue{--xr-font-color0:var(--sy-c-heading);--xr-font-color2:var(--sy-c-text);--xr-font-color3:var(--sy-c-light);--xr-border-color:var(--sy-c-border);--xr-disabled-color:var(--gray-a6);--xr-background-color:var(--sy-c-background);--xr-background-color-row-even:var(--sy-c-background);--xr-background-color-row-odd:var(--gray-2)}.yue .xr-array-data pre{margin:0}@media not all and (min-width:640px){.max-sm\:max-w-full{max-width:100%}}@media (min-width:768px){.md\:sticky{position:sticky}.md\:inline{display:inline}.md\:flex{display:flex}.md\:hidden{display:none}.md\:w-72{width:18rem}.md\:shrink-0{flex-shrink:0}}@media (min-width:1280px){.xl\:sticky{position:sticky}.xl\:top-16{top:4rem}.xl\:hidden{display:none}.xl\:px-12{padding-left:3rem;padding-right:3rem}.xl\:pl-0{padding-left:0}}@media print{.print\:hidden{display:none}.print\:pt-6{padding-top:1.5rem}} \ No newline at end of file diff --git a/_static/shibuya.js b/_static/shibuya.js new file mode 100644 index 00000000..10119a4a --- /dev/null +++ b/_static/shibuya.js @@ -0,0 +1,41 @@ +(()=>{var $t=Object.freeze({left:0,top:0,width:16,height:16}),$=Object.freeze({rotate:0,vFlip:!1,hFlip:!1}),P=Object.freeze({...$t,...$}),tt=Object.freeze({...P,body:"",hidden:!1}),me=Object.freeze({width:null,height:null}),Bt=Object.freeze({...me,...$});function be(t,e=0){let n=t.replace(/^-?[0-9.]*/,"");function s(o){for(;o<0;)o+=4;return o%4}if(n===""){let o=parseInt(t);return isNaN(o)?0:s(o)}else if(n!==t){let o=0;switch(n){case"%":o=25;break;case"deg":o=90}if(o){let r=parseFloat(t.slice(0,t.length-n.length));return isNaN(r)?0:(r=r/o,r%1===0?s(r):0)}}return e}var ye=/[\s,]+/;function ve(t,e){e.split(ye).forEach(n=>{switch(n.trim()){case"horizontal":t.hFlip=!0;break;case"vertical":t.vFlip=!0;break}})}var Dt={...Bt,preserveAspectRatio:""};function vt(t){let e={...Dt},n=(s,o)=>t.getAttribute(s)||o;return e.width=n("width",null),e.height=n("height",null),e.rotate=be(n("rotate","")),ve(e,n("flip","")),e.preserveAspectRatio=n("preserveAspectRatio",n("preserveaspectratio","")),e}function we(t,e){for(let n in Dt)if(t[n]!==e[n])return!0;return!1}var Ht=/^[a-z0-9]+(-[a-z0-9]+)*$/,M=(t,e,n,s="")=>{let o=t.split(":");if(t.slice(0,1)==="@"){if(o.length<2||o.length>3)return null;s=o.shift().slice(1)}if(o.length>3||!o.length)return null;if(o.length>1){let i=o.pop(),a=o.pop(),l={provider:o.length>0?o[0]:s,prefix:a,name:i};return e&&!q(l)?null:l}let r=o[0],c=r.split("-");if(c.length>1){let i={provider:s,prefix:c.shift(),name:c.join("-")};return e&&!q(i)?null:i}if(n&&s===""){let i={provider:s,prefix:"",name:r};return e&&!q(i,n)?null:i}return null},q=(t,e)=>t?!!((e&&t.prefix===""||t.prefix)&&t.name):!1;function Ie(t,e){let n={};!t.hFlip!=!e.hFlip&&(n.hFlip=!0),!t.vFlip!=!e.vFlip&&(n.vFlip=!0);let s=((t.rotate||0)+(e.rotate||0))%4;return s&&(n.rotate=s),n}function wt(t,e){let n=Ie(t,e);for(let s in tt)s in $?s in t&&!(s in n)&&(n[s]=$[s]):s in e?n[s]=e[s]:s in t&&(n[s]=t[s]);return n}function Se(t,e){let n=t.icons,s=t.aliases||Object.create(null),o=Object.create(null);function r(c){if(n[c])return o[c]=[];if(!(c in o)){o[c]=null;let i=s[c]&&s[c].parent,a=i&&r(i);a&&(o[c]=[i].concat(a))}return o[c]}return Object.keys(n).concat(Object.keys(s)).forEach(r),o}function xe(t,e,n){let s=t.icons,o=t.aliases||Object.create(null),r={};function c(i){r=wt(s[i]||o[i],r)}return c(e),n.forEach(c),wt(t,r)}function Qt(t,e){let n=[];if(typeof t!="object"||typeof t.icons!="object")return n;t.not_found instanceof Array&&t.not_found.forEach(o=>{e(o,null),n.push(o)});let s=Se(t);for(let o in s){let r=s[o];r&&(e(o,xe(t,o,r)),n.push(o))}return n}var Ce={provider:"",aliases:{},not_found:{},...$t};function W(t,e){for(let n in e)if(n in t&&typeof t[n]!=typeof e[n])return!1;return!0}function Ut(t){if(typeof t!="object"||t===null)return null;let e=t;if(typeof e.prefix!="string"||!t.icons||typeof t.icons!="object"||!W(t,Ce))return null;let n=e.icons;for(let o in n){let r=n[o];if(!o||typeof r.body!="string"||!W(r,tt))return null}let s=e.aliases||Object.create(null);for(let o in s){let r=s[o],c=r.parent;if(!o||typeof c!="string"||!n[c]&&!s[c]||!W(r,tt))return null}return e}var B=Object.create(null);function Ae(t,e){return{provider:t,prefix:e,icons:Object.create(null),missing:new Set}}function v(t,e){let n=B[t]||(B[t]=Object.create(null));return n[e]||(n[e]=Ae(t,e))}function lt(t,e){return Ut(e)?Qt(e,(n,s)=>{s?t.icons[n]=s:t.missing.add(n)}):[]}function _e(t,e,n){try{if(typeof n.body=="string")return t.icons[e]={...n},!0}catch{}return!1}function ke(t,e){let n=[];return(typeof t=="string"?[t]:Object.keys(B)).forEach(o=>{(typeof o=="string"&&typeof e=="string"?[e]:Object.keys(B[o]||{})).forEach(c=>{let i=v(o,c);n=n.concat(Object.keys(i.icons).map(a=>(o!==""?"@"+o+":":"")+c+":"+a))})}),n}var O=!1;function Vt(t){return typeof t=="boolean"&&(O=t),O}function T(t){let e=typeof t=="string"?M(t,!0,O):t;if(e){let n=v(e.provider,e.prefix),s=e.name;return n.icons[s]||(n.missing.has(s)?null:void 0)}}function zt(t,e){let n=M(t,!0,O);if(!n)return!1;let s=v(n.provider,n.prefix);return e?_e(s,n.name,e):(s.missing.add(n.name),!0)}function It(t,e){if(typeof t!="object")return!1;if(typeof e!="string"&&(e=t.provider||""),O&&!e&&!t.prefix){let o=!1;return Ut(t)&&(t.prefix="",Qt(t,(r,c)=>{zt(r,c)&&(o=!0)})),o}let n=t.prefix;if(!q({provider:e,prefix:n,name:"a"}))return!1;let s=v(e,n);return!!lt(s,t)}function St(t){return!!T(t)}function Le(t){let e=T(t);return e&&{...P,...e}}function Ee(t){let e={loaded:[],missing:[],pending:[]},n=Object.create(null);t.sort((o,r)=>o.provider!==r.provider?o.provider.localeCompare(r.provider):o.prefix!==r.prefix?o.prefix.localeCompare(r.prefix):o.name.localeCompare(r.name));let s={provider:"",prefix:"",name:""};return t.forEach(o=>{if(s.name===o.name&&s.prefix===o.prefix&&s.provider===o.provider)return;s=o;let r=o.provider,c=o.prefix,i=o.name,a=n[r]||(n[r]=Object.create(null)),l=a[c]||(a[c]=v(r,c)),u;i in l.icons?u=e.loaded:c===""||l.missing.has(i)?u=e.missing:u=e.pending;let f={provider:r,prefix:c,name:i};u.push(f)}),e}function Gt(t,e){t.forEach(n=>{let s=n.loaderCallbacks;s&&(n.loaderCallbacks=s.filter(o=>o.id!==e))})}function Oe(t){t.pendingCallbacksFlag||(t.pendingCallbacksFlag=!0,setTimeout(()=>{t.pendingCallbacksFlag=!1;let e=t.loaderCallbacks?t.loaderCallbacks.slice(0):[];if(!e.length)return;let n=!1,s=t.provider,o=t.prefix;e.forEach(r=>{let c=r.icons,i=c.pending.length;c.pending=c.pending.filter(a=>{if(a.prefix!==o)return!0;let l=a.name;if(t.icons[l])c.loaded.push({provider:s,prefix:o,name:l});else if(t.missing.has(l))c.missing.push({provider:s,prefix:o,name:l});else return n=!0,!0;return!1}),c.pending.length!==i&&(n||Gt([t],r.id),r.callback(c.loaded.slice(0),c.missing.slice(0),c.pending.slice(0),r.abort))})}))}var Te=0;function je(t,e,n){let s=Te++,o=Gt.bind(null,n,s);if(!e.pending.length)return o;let r={id:s,icons:e,callback:t,abort:o};return n.forEach(c=>{(c.loaderCallbacks||(c.loaderCallbacks=[])).push(r)}),o}var et=Object.create(null);function xt(t,e){et[t]=e}function nt(t){return et[t]||et[""]}function Pe(t,e=!0,n=!1){let s=[];return t.forEach(o=>{let r=typeof o=="string"?M(o,e,n):o;r&&s.push(r)}),s}var Me={resources:[],index:0,timeout:2e3,rotate:750,random:!1,dataAfterTimeout:!1};function Ne(t,e,n,s){let o=t.resources.length,r=t.random?Math.floor(Math.random()*o):t.index,c;if(t.random){let h=t.resources.slice(0);for(c=[];h.length>1;){let b=Math.floor(Math.random()*h.length);c.push(h[b]),h=h.slice(0,b).concat(h.slice(b+1))}c=c.concat(h)}else c=t.resources.slice(r).concat(t.resources.slice(0,r));let i=Date.now(),a="pending",l=0,u,f=null,d=[],p=[];typeof s=="function"&&p.push(s);function w(){f&&(clearTimeout(f),f=null)}function I(){a==="pending"&&(a="aborted"),w(),d.forEach(h=>{h.status==="pending"&&(h.status="aborted")}),d=[]}function g(h,b){b&&(p=[]),typeof h=="function"&&p.push(h)}function Y(){return{startTime:i,payload:e,status:a,queriesSent:l,queriesPending:d.length,subscribe:g,abort:I}}function S(){a="failed",p.forEach(h=>{h(void 0,u)})}function y(){d.forEach(h=>{h.status==="pending"&&(h.status="aborted")}),d=[]}function m(h,b,_){let N=b!=="success";switch(d=d.filter(x=>x!==h),a){case"pending":break;case"failed":if(N||!t.dataAfterTimeout)return;break;default:return}if(b==="abort"){u=_,S();return}if(N){u=_,d.length||(c.length?K():S());return}if(w(),y(),!t.random){let x=t.resources.indexOf(h.resource);x!==-1&&x!==t.index&&(t.index=x)}a="completed",p.forEach(x=>{x(_)})}function K(){if(a!=="pending")return;w();let h=c.shift();if(h===void 0){if(d.length){f=setTimeout(()=>{w(),a==="pending"&&(y(),S())},t.timeout);return}S();return}let b={status:"pending",resource:h,callback:(_,N)=>{m(b,_,N)}};d.push(b),l++,f=setTimeout(K,t.rotate),n(h,e,b.callback)}return setTimeout(K),Y}function Jt(t){let e={...Me,...t},n=[];function s(){n=n.filter(i=>i().status==="pending")}function o(i,a,l){let u=Ne(e,i,a,(f,d)=>{s(),l&&l(f,d)});return n.push(u),u}function r(i){return n.find(a=>i(a))||null}return{query:o,find:r,setIndex:i=>{e.index=i},getIndex:()=>e.index,cleanup:s}}function ut(t){let e;if(typeof t.resources=="string")e=[t.resources];else if(e=t.resources,!(e instanceof Array)||!e.length)return null;return{resources:e,path:t.path||"/",maxURL:t.maxURL||500,rotate:t.rotate||750,timeout:t.timeout||5e3,random:t.random===!0,index:t.index||0,dataAfterTimeout:t.dataAfterTimeout!==!1}}var H=Object.create(null),k=["https://api.simplesvg.com","https://api.unisvg.com"],F=[];for(;k.length>0;)k.length===1||Math.random()>.5?F.push(k.shift()):F.push(k.pop());H[""]=ut({resources:["https://api.iconify.design"].concat(F)});function Ct(t,e){let n=ut(e);return n===null?!1:(H[t]=n,!0)}function Q(t){return H[t]}function Re(){return Object.keys(H)}function At(){}var X=Object.create(null);function qe(t){if(!X[t]){let e=Q(t);if(!e)return;let n=Jt(e),s={config:e,redundancy:n};X[t]=s}return X[t]}function Yt(t,e,n){let s,o;if(typeof t=="string"){let r=nt(t);if(!r)return n(void 0,424),At;o=r.send;let c=qe(t);c&&(s=c.redundancy)}else{let r=ut(t);if(r){s=Jt(r);let c=t.resources?t.resources[0]:"",i=nt(c);i&&(o=i.send)}}return!s||!o?(n(void 0,424),At):s.query(e,o,n)().abort}var _t="iconify2",j="iconify",Kt=j+"-count",kt=j+"-version",Wt=36e5,Fe=168,$e=50;function ot(t,e){try{return t.getItem(e)}catch{}}function ft(t,e,n){try{return t.setItem(e,n),!0}catch{}}function Lt(t,e){try{t.removeItem(e)}catch{}}function st(t,e){return ft(t,Kt,e.toString())}function rt(t){return parseInt(ot(t,Kt))||0}var C={local:!0,session:!0},Xt={local:new Set,session:new Set},dt=!1;function Be(t){dt=t}var R=typeof window>"u"?{}:window;function Zt(t){let e=t+"Storage";try{if(R&&R[e]&&typeof R[e].length=="number")return R[e]}catch{}C[t]=!1}function te(t,e){let n=Zt(t);if(!n)return;let s=ot(n,kt);if(s!==_t){if(s){let i=rt(n);for(let a=0;a{let a=j+i.toString(),l=ot(n,a);if(typeof l=="string"){try{let u=JSON.parse(l);if(typeof u=="object"&&typeof u.cached=="number"&&u.cached>o&&typeof u.provider=="string"&&typeof u.data=="object"&&typeof u.data.prefix=="string"&&e(u,i))return!0}catch{}Lt(n,a)}},c=rt(n);for(let i=c-1;i>=0;i--)r(i)||(i===c-1?(c--,st(n,c)):Xt[t].add(i))}function ee(){if(!dt){Be(!0);for(let t in C)te(t,e=>{let n=e.data,s=e.provider,o=n.prefix,r=v(s,o);if(!lt(r,n).length)return!1;let c=n.lastModified||-1;return r.lastModifiedCached=r.lastModifiedCached?Math.min(r.lastModifiedCached,c):c,!0})}}function De(t,e){let n=t.lastModifiedCached;if(n&&n>=e)return n===e;if(t.lastModifiedCached=e,n)for(let s in C)te(s,o=>{let r=o.data;return o.provider!==t.provider||r.prefix!==t.prefix||r.lastModified===e});return!0}function He(t,e){dt||ee();function n(s){let o;if(!C[s]||!(o=Zt(s)))return;let r=Xt[s],c;if(r.size)r.delete(c=Array.from(r).shift());else if(c=rt(o),c>=$e||!st(o,c+1))return;let i={cached:Math.floor(Date.now()/Wt),provider:t.provider,data:e};return ft(o,j+c.toString(),JSON.stringify(i))}e.lastModified&&!De(t,e.lastModified)||Object.keys(e.icons).length&&(e.not_found&&(e=Object.assign({},e),delete e.not_found),n("local")||n("session"))}function Et(){}function Qe(t){t.iconsLoaderFlag||(t.iconsLoaderFlag=!0,setTimeout(()=>{t.iconsLoaderFlag=!1,Oe(t)}))}function Ue(t){let e=[],n=[];return t.forEach(s=>{(s.match(Ht)?e:n).push(s)}),{valid:e,invalid:n}}function L(t,e,n,s){function o(){let r=t.pendingIcons;e.forEach(c=>{r&&r.delete(c),t.icons[c]||t.missing.add(c)})}if(n&&typeof n=="object")try{if(!lt(t,n).length){o();return}s&&He(t,n)}catch(r){console.error(r)}o(),Qe(t)}function Ot(t,e){t instanceof Promise?t.then(n=>{e(n)}).catch(()=>{e(null)}):e(t)}function Ve(t,e){t.iconsToLoad?t.iconsToLoad=t.iconsToLoad.concat(e).sort():t.iconsToLoad=e,t.iconsQueueFlag||(t.iconsQueueFlag=!0,setTimeout(()=>{t.iconsQueueFlag=!1;let{provider:n,prefix:s}=t,o=t.iconsToLoad;if(delete t.iconsToLoad,!o||!o.length)return;let r=t.loadIcon;if(t.loadIcons&&(o.length>1||!r)){Ot(t.loadIcons(o,s,n),u=>{L(t,o,u,!1)});return}if(r){o.forEach(u=>{let f=r(u,s,n);Ot(f,d=>{let p=d?{prefix:s,icons:{[u]:d}}:null;L(t,[u],p,!1)})});return}let{valid:c,invalid:i}=Ue(o);if(i.length&&L(t,i,null,!1),!c.length)return;let a=s.match(Ht)?nt(n):null;if(!a){L(t,c,null,!1);return}a.prepare(n,s,c).forEach(u=>{Yt(n,u,f=>{L(t,u.icons,f,!0)})})}))}var ht=(t,e)=>{let n=Pe(t,!0,Vt()),s=Ee(n);if(!s.pending.length){let a=!0;return e&&setTimeout(()=>{a&&e(s.loaded,s.missing,s.pending,Et)}),()=>{a=!1}}let o=Object.create(null),r=[],c,i;return s.pending.forEach(a=>{let{provider:l,prefix:u}=a;if(u===i&&l===c)return;c=l,i=u,r.push(v(l,u));let f=o[l]||(o[l]=Object.create(null));f[u]||(f[u]=[])}),s.pending.forEach(a=>{let{provider:l,prefix:u,name:f}=a,d=v(l,u),p=d.pendingIcons||(d.pendingIcons=new Set);p.has(f)||(p.add(f),o[l][u].push(f))}),r.forEach(a=>{let l=o[a.provider][a.prefix];l.length&&Ve(a,l)}),e?je(e,s,r):Et},ze=t=>new Promise((e,n)=>{let s=typeof t=="string"?M(t,!0):t;if(!s){n(t);return}ht([s||t],o=>{if(o.length&&s){let r=T(s);if(r){e({...P,...r});return}}n(t)})});function Tt(t){try{let e=typeof t=="string"?JSON.parse(t):t;if(typeof e.body=="string")return{...e}}catch{}}function Ge(t,e){if(typeof t=="object")return{data:Tt(t),value:t};if(typeof t!="string")return{value:t};if(t.includes("{")){let r=Tt(t);if(r)return{data:r,value:t}}let n=M(t,!0,!0);if(!n)return{value:t};let s=T(n);if(s!==void 0||!n.prefix)return{value:t,name:n,data:s};let o=ht([n],()=>e(t,n,T(n)));return{value:t,name:n,loading:o}}var ne=!1;try{ne=navigator.vendor.indexOf("Apple")===0}catch{}function Je(t,e){switch(e){case"svg":case"bg":case"mask":return e}return e!=="style"&&(ne||t.indexOf("=0;){let o=t.indexOf(">",s),r=t.indexOf("",r);if(c===-1)break;n+=t.slice(o+1,r).trim(),t=t.slice(0,s).trim()+t.slice(c+1)}return{defs:n,content:t}}function Xe(t,e){return t?""+t+""+e:e}function Ze(t,e,n){let s=We(t);return Xe(s.defs,e+s.content+n)}var tn=t=>t==="unset"||t==="undefined"||t==="none";function oe(t,e){let n={...P,...t},s={...Bt,...e},o={left:n.left,top:n.top,width:n.width,height:n.height},r=n.body;[n,s].forEach(I=>{let g=[],Y=I.hFlip,S=I.vFlip,y=I.rotate;Y?S?y+=2:(g.push("translate("+(o.width+o.left).toString()+" "+(0-o.top).toString()+")"),g.push("scale(-1 1)"),o.top=o.left=0):S&&(g.push("translate("+(0-o.left).toString()+" "+(o.height+o.top).toString()+")"),g.push("scale(1 -1)"),o.top=o.left=0);let m;switch(y<0&&(y-=Math.floor(y/4)*4),y=y%4,y){case 1:m=o.height/2+o.top,g.unshift("rotate(90 "+m.toString()+" "+m.toString()+")");break;case 2:g.unshift("rotate(180 "+(o.width/2+o.left).toString()+" "+(o.height/2+o.top).toString()+")");break;case 3:m=o.width/2+o.left,g.unshift("rotate(-90 "+m.toString()+" "+m.toString()+")");break}y%2===1&&(o.left!==o.top&&(m=o.left,o.left=o.top,o.top=m),o.width!==o.height&&(m=o.width,o.width=o.height,o.height=m)),g.length&&(r=Ze(r,'',""))});let c=s.width,i=s.height,a=o.width,l=o.height,u,f;c===null?(f=i===null?"1em":i==="auto"?l:i,u=it(f,a/l)):(u=c==="auto"?a:c,f=i===null?it(u,l/a):i==="auto"?l:i);let d={},p=(I,g)=>{tn(g)||(d[I]=g.toString())};p("width",u),p("height",f);let w=[o.left,o.top,a,l];return d.viewBox=w.join(" "),{attributes:d,viewBox:w,body:r}}function pt(t,e){let n=t.indexOf("xlink:")===-1?"":' xmlns:xlink="http://www.w3.org/1999/xlink"';for(let s in e)n+=" "+s+'="'+e[s]+'"';return'"+t+""}function en(t){return t.replace(/"/g,"'").replace(/%/g,"%25").replace(/#/g,"%23").replace(//g,"%3E").replace(/\s+/g," ")}function nn(t){return"data:image/svg+xml,"+en(t)}function se(t){return'url("'+nn(t)+'")'}var on=()=>{let t;try{if(t=fetch,typeof t=="function")return t}catch{}},D=on();function sn(t){D=t}function rn(){return D}function cn(t,e){let n=Q(t);if(!n)return 0;let s;if(!n.maxURL)s=0;else{let o=0;n.resources.forEach(c=>{o=Math.max(o,c.length)});let r=e+".json?icons=";s=n.maxURL-o-n.path.length-r.length}return s}function an(t){return t===404}var ln=(t,e,n)=>{let s=[],o=cn(t,e),r="icons",c={type:r,provider:t,prefix:e,icons:[]},i=0;return n.forEach((a,l)=>{i+=a.length+1,i>=o&&l>0&&(s.push(c),c={type:r,provider:t,prefix:e,icons:[]},i=a.length),c.icons.push(a)}),s.push(c),s};function un(t){if(typeof t=="string"){let e=Q(t);if(e)return e.path}return"/"}var fn=(t,e,n)=>{if(!D){n("abort",424);return}let s=un(e.provider);switch(e.type){case"icons":{let r=e.prefix,i=e.icons.join(","),a=new URLSearchParams({icons:i});s+=r+".json?"+a.toString();break}case"custom":{let r=e.uri;s+=r.slice(0,1)==="/"?r.slice(1):r;break}default:n("abort",400);return}let o=503;D(t+s).then(r=>{let c=r.status;if(c!==200){setTimeout(()=>{n(an(c)?"abort":"next",c)});return}return o=501,r.json()}).then(r=>{if(typeof r!="object"||r===null){setTimeout(()=>{r===404?n("abort",r):n("next",o)});return}setTimeout(()=>{n("success",r)})}).catch(()=>{n("next",o)})},dn={prepare:ln,send:fn};function hn(t,e,n){v(n||"",e).loadIcons=t}function pn(t,e,n){v(n||"",e).loadIcon=t}function jt(t,e){switch(t){case"local":case"session":C[t]=e;break;case"all":for(let n in C)C[n]=e;break}}var Z="data-style",re="";function gn(t){re=t}function Pt(t,e){let n=Array.from(t.childNodes).find(s=>s.hasAttribute&&s.hasAttribute(Z));n||(n=document.createElement("style"),n.setAttribute(Z,Z),t.appendChild(n)),n.textContent=":host{display:inline-block;vertical-align:"+(e?"-0.125em":"0")+"}span,svg{display:block;margin:auto}"+re}function ie(){xt("",dn),Vt(!0);let t;try{t=window}catch{}if(t){if(ee(),t.IconifyPreload!==void 0){let n=t.IconifyPreload,s="Invalid IconifyPreload syntax.";typeof n=="object"&&n!==null&&(n instanceof Array?n:[n]).forEach(o=>{try{(typeof o!="object"||o===null||o instanceof Array||typeof o.icons!="object"||typeof o.prefix!="string"||!It(o))&&console.error(s)}catch{console.error(s)}})}if(t.IconifyProviders!==void 0){let n=t.IconifyProviders;if(typeof n=="object"&&n!==null)for(let s in n){let o="IconifyProviders["+s+"] is invalid.";try{let r=n[s];if(typeof r!="object"||!r||r.resources===void 0)continue;Ct(s,r)||console.error(o)}catch{console.error(o)}}}}return{enableCache:n=>jt(n,!0),disableCache:n=>jt(n,!1),iconLoaded:St,iconExists:St,getIcon:Le,listIcons:ke,addIcon:zt,addCollection:It,calculateSize:it,buildIcon:oe,iconToHTML:pt,svgToURL:se,loadIcons:ht,loadIcon:ze,addAPIProvider:Ct,setCustomIconLoader:pn,setCustomIconsLoader:hn,appendCustomStyle:gn,_api:{getAPIConfig:Q,setAPIModule:xt,sendAPIQuery:Yt,setFetch:sn,getFetch:rn,listAPIProviders:Re}}}var ct={"background-color":"currentColor"},ce={"background-color":"transparent"},Mt={image:"var(--svg)",repeat:"no-repeat",size:"100% 100%"},Nt={"-webkit-mask":ct,mask:ct,background:ce};for(let t in Nt){let e=Nt[t];for(let n in Mt)e[t+"-"+n]=Mt[n]}function Rt(t){return t?t+(t.match(/^[-0-9.]+$/)?"px":""):"inherit"}function mn(t,e,n){let s=document.createElement("span"),o=t.body;o.indexOf("");let r=t.attributes,c=pt(o,{...r,width:e.width+"",height:e.height+""}),i=se(c),a=s.style,l={"--svg":i,width:Rt(r.width),height:Rt(r.height),...n?ct:ce};for(let u in l)a.setProperty(u,l[u]);return s}var E;function bn(){try{E=window.trustedTypes.createPolicy("iconify",{createHTML:t=>t})}catch{E=null}}function yn(t){return E===void 0&&bn(),E?E.createHTML(t):t}function vn(t){let e=document.createElement("span"),n=t.attributes,s="";n.width||(s="width: inherit;"),n.height||(s+="height: inherit;"),s&&(n.style=s);let o=pt(t.body,n);return e.innerHTML=yn(o),e.firstChild}function at(t){return Array.from(t.childNodes).find(e=>{let n=e.tagName&&e.tagName.toUpperCase();return n==="SPAN"||n==="SVG"})}function qt(t,e){let n=e.icon.data,s=e.customisations,o=oe(n,s);s.preserveAspectRatio&&(o.attributes.preserveAspectRatio=s.preserveAspectRatio);let r=e.renderedMode,c;switch(r){case"svg":c=vn(o);break;default:c=mn(o,{...P,...n},r==="mask")}let i=at(t);i?c.tagName==="SPAN"&&i.tagName===c.tagName?i.setAttribute("style",c.getAttribute("style")):t.replaceChild(c,i):t.appendChild(c)}function Ft(t,e,n){let s=n&&(n.rendered?n:n.lastRender);return{rendered:!1,inline:e,icon:t,lastRender:s}}function wn(t="iconify-icon"){let e,n;try{e=window.customElements,n=window.HTMLElement}catch{return}if(!e||!n)return;let s=e.get(t);if(s)return s;let o=["icon","mode","inline","noobserver","width","height","rotate","flip"],r=class extends n{_shadowRoot;_initialised=!1;_state;_checkQueued=!1;_connected=!1;_observer=null;_visible=!0;constructor(){super();let i=this._shadowRoot=this.attachShadow({mode:"open"}),a=this.hasAttribute("inline");Pt(i,a),this._state=Ft({value:""},a),this._queueCheck()}connectedCallback(){this._connected=!0,this.startObserver()}disconnectedCallback(){this._connected=!1,this.stopObserver()}static get observedAttributes(){return o.slice(0)}attributeChangedCallback(i){switch(i){case"inline":{let a=this.hasAttribute("inline"),l=this._state;a!==l.inline&&(l.inline=a,Pt(this._shadowRoot,a));break}case"noobserver":{this.hasAttribute("noobserver")?this.startObserver():this.stopObserver();break}default:this._queueCheck()}}get icon(){let i=this.getAttribute("icon");if(i&&i.slice(0,1)==="{")try{return JSON.parse(i)}catch{}return i}set icon(i){typeof i=="object"&&(i=JSON.stringify(i)),this.setAttribute("icon",i)}get inline(){return this.hasAttribute("inline")}set inline(i){i?this.setAttribute("inline","true"):this.removeAttribute("inline")}get observer(){return this.hasAttribute("observer")}set observer(i){i?this.setAttribute("observer","true"):this.removeAttribute("observer")}restartAnimation(){let i=this._state;if(i.rendered){let a=this._shadowRoot;if(i.renderedMode==="svg")try{a.lastChild.setCurrentTime(0);return}catch{}qt(a,i)}}get status(){let i=this._state;return i.rendered?"rendered":i.icon.data===null?"failed":"loading"}_queueCheck(){this._checkQueued||(this._checkQueued=!0,setTimeout(()=>{this._check()}))}_check(){if(!this._checkQueued)return;this._checkQueued=!1;let i=this._state,a=this.getAttribute("icon");if(a!==i.icon.value){this._iconChanged(a);return}if(!i.rendered||!this._visible)return;let l=this.getAttribute("mode"),u=vt(this);(i.attrMode!==l||we(i.customisations,u)||!at(this._shadowRoot))&&this._renderIcon(i.icon,u,l)}_iconChanged(i){let a=Ge(i,(l,u,f)=>{let d=this._state;if(d.rendered||this.getAttribute("icon")!==l)return;let p={value:l,name:u,data:f};p.data?this._gotIconData(p):d.icon=p});a.data?this._gotIconData(a):this._state=Ft(a,this._state.inline,this._state)}_forceRender(){if(!this._visible){let i=at(this._shadowRoot);i&&this._shadowRoot.removeChild(i);return}this._queueCheck()}_gotIconData(i){this._checkQueued=!1,this._renderIcon(i,vt(this),this.getAttribute("mode"))}_renderIcon(i,a,l){let u=Je(i.data.body,l),f=this._state.inline;qt(this._shadowRoot,this._state={rendered:!0,icon:i,inline:f,customisations:a,attrMode:l,renderedMode:u})}startObserver(){if(!this._observer&&!this.hasAttribute("noobserver"))try{this._observer=new IntersectionObserver(i=>{let a=i.some(l=>l.isIntersecting);a!==this._visible&&(this._visible=a,this._forceRender())}),this._observer.observe(this)}catch{if(this._observer){try{this._observer.disconnect()}catch{}this._observer=null}}}stopObserver(){this._observer&&(this._observer.disconnect(),this._observer=null,this._visible=!0,this._connected&&this._forceRender())}};o.forEach(i=>{i in r.prototype||Object.defineProperty(r.prototype,i,{get:function(){return this.getAttribute(i)},set:function(a){a!==null?this.setAttribute(i,a):this.removeAttribute(i)}})});let c=ie();for(let i in c)r[i]=r.prototype[i]=c[i];return e.define(t,r),r}var In=wn()||ie(),{enableCache:Nn,disableCache:Rn,iconLoaded:qn,iconExists:Fn,getIcon:$n,listIcons:Bn,addIcon:Dn,addCollection:Hn,calculateSize:Qn,buildIcon:Un,iconToHTML:Vn,svgToURL:zn,loadIcons:Gn,loadIcon:Jn,setCustomIconLoader:Yn,setCustomIconsLoader:Kn,addAPIProvider:Wn,_api:Xn}=In;function Sn(t){let e=t.getAttribute("aria-controls"),n=document.getElementById(e),s="data-expanded-"+e;t.addEventListener("click",function(){document.body.hasAttribute(s)?(document.body.removeAttribute(s),n.classList.remove("_expanded"),ae(e,"false")):(document.body.setAttribute(s,"true"),n.classList.add("_expanded"),ae(e,"true"))})}function ae(t,e){let n=document.querySelectorAll('[aria-controls="'+t+'"]');for(el of n)el.setAttribute("aria-expanded",e)}var le=document.querySelectorAll(".js-menu");for(let t=0;t{U.parentNode.removeChild(U),document.head.removeChild(t)}),e(),window.addEventListener("resize",e)}var Cn;var V=["auto","light","dark"],z=document.querySelector(".js-theme");function An(){let t=ue();t+=1,V[t]||(t=0);let e=V[t];setColorMode(e),localStorage._theme=e,fe(e)}function ue(){return V.indexOf(document.documentElement.getAttribute("data-color-mode")||"auto")}function fe(t){let e=z.getAttribute("data-aria-"+t);z.setAttribute("aria-label",e)}z&&(z.addEventListener("click",An),fe(V[ue()]||"auto"));function _n(){let t=document.querySelector(".globaltoc");if(!t)return;let e=parseInt(t.getAttribute("data-expand-depth"),10),n=o=>{if(!e)return!1;let r=0;for(;o.parentNode&&o.parentNode!==t;)o=o.parentNode,o.nodeName==="UL"&&(r+=1);return e>=r};t.querySelectorAll("li > ul").forEach(o=>{let r=o.parentNode;r.classList.contains("current")||n(r)?r.classList.add("_expand"):r.classList.add("_collapse");let c=kn(o);r.appendChild(c)})}function kn(t){let e=document.createElement("button");e.innerHTML='';let n=t.parentNode,s=t.previousSibling,o=s.textContent,r=()=>{n.classList.contains("_expand")?e.setAttribute("aria-label","Collapse "+o):e.setAttribute("aria-label","Expand "+o)};r();let c=i=>{i.preventDefault(),n.classList.contains("_expand")?(n.classList.remove("_expand"),n.classList.add("_collapse")):(n.classList.remove("_collapse"),n.classList.add("_expand")),r()};return s.getAttribute("href")==="#"&&s.addEventListener("click",c),e.addEventListener("click",c),e}var gt=document.querySelector(".globaltoc a.current");gt&>.scrollIntoViewIfNeeded&>.scrollIntoViewIfNeeded();_n();var de=0,mt=200,G=document.querySelectorAll(".yue > section section[id]"),A=document.querySelector(".back-to-top");function he(){let t=document.querySelector(".yue > section");t&&(mt=t.computedStyleMap().get("scroll-margin-top").value)}function Ln(t){let e=t.getBoundingClientRect();return e.top<=mt&&e.bottom>=mt}function pe(t){document.querySelectorAll(".localtoc li.active").forEach(e=>{e.classList.remove("active")}),document.querySelector(`.localtoc a[href="#${t}"]`).parentNode.classList.add("active")}function ge(){let t;for(let e=0;e=document.body.offsetHeight){let t=G[G.length-1];t&&pe(t.id)}else ge();A&&(window.scrollY&&window.scrollY{window.scrollTo(0,0)});window.addEventListener("scroll",En);window.addEventListener("DOMContentLoaded",()=>{he(),ge()});window.addEventListener("resize",he);var J=document.querySelector(".js-repo-stats");async function On(t,e){let n=`https://api.github.com/repos/${t}/${e}`,o=await(await fetch(n)).json(),r={stars:o.watchers,forks:o.forks};bt(r),sessionStorage.setItem("_sy/repo/stats",JSON.stringify(r))}async function Tn(t,e){let n="https://gitlab.com/api/v4/projects/"+encodeURIComponent(t+"/"+e),o=await(await fetch(n)).json(),r={stars:o.star_count,forks:o.forks_count};bt(r),sessionStorage.setItem("_sy/repo/stats",JSON.stringify(r))}function bt({stars:t,forks:e}){t&&(document.querySelector(".js-repo-stars").textContent=t),e&&(document.querySelector(".js-repo-forks").textContent=e)}function jn(){let t=sessionStorage.getItem("_sy/repo/stats");if(t)bt(JSON.parse(t));else{let e=J.getAttribute("data-user"),n=J.getAttribute("data-repo"),s=J.getAttribute("data-type");s==="github"?On(e,n):s==="gitlab"&&Tn(e,n)}}J&&jn();function Pn(t,e){let n=document.createElement("script");n.id="_carbonads_js",n.src=`//cdn.carbonads.com/carbon.js?serve=${t}&placement=${e}`;let s=document.querySelector(".yue > section"),o=document.querySelector(".yue > section > section");if(o)s.insertBefore(n,o);else{let r=document.querySelector(".yue > section > p");r?s.insertBefore(n,r.nextSibling):s.appendChild(n)}}var yt=document.querySelector(".js-carbon");if(yt){let t=yt.getAttribute("data-carbon-code"),e=yt.getAttribute("data-carbon-placement");t&&e&&Pn(t,e)}var Mn=` +:host > div .results .hit h2 { + color: var(--sy-c-heading); + margin-bottom: 0; + border-bottom: 0; + font-weight: 600; +} +:host > div .results .hit .hit-block .content { + color: var(--sy-c-text); +} +:host > div .results .hit-block a.hit:hover, :host > div .results .hit-block .hit.active { + background-color: var(--gray-5); + border-radius: 4px; +} + +:host > div div.hit-block a.hit-block-heading:hover { + text-decoration: underline; +} + +:host > div div.hit-block a.hit-block-heading i, +:host > div div.hit-block .hit-block-heading-container .close-icon { + color: var(--sy-c-light); + margin-bottom: 0; + display: flex; +} +`;document.addEventListener("readthedocs-addons-data-ready",function(t){document.querySelector(".searchbox input").addEventListener("focusin",()=>{let e=new CustomEvent("readthedocs-search-show");document.dispatchEvent(e)}),setTimeout(()=>{let e=document.querySelector("readthedocs-search");if(e){let n=document.createElement("style");n.textContent=Mn,e.shadowRoot.appendChild(n)}},1e3)});/windows/i.test(navigator.userAgent)&&document.body.classList.add("win");})(); +/*! Bundled license information: + +iconify-icon/dist/iconify-icon.mjs: + (** + * (c) Iconify + * + * For the full copyright and license information, please view the license.txt + * files at https://github.com/iconify/iconify + * + * Licensed under MIT. + * + * @license MIT + * @version 2.2.0 + *) +*/ diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 00000000..aae669d7 --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,144 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + parent.insertBefore( + span, + parent.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(SphinxHighlight.highlightSearchWords); +_ready(SphinxHighlight.initEscapeListener); diff --git a/architecture.html b/architecture.html new file mode 100644 index 00000000..021a6e31 --- /dev/null +++ b/architecture.html @@ -0,0 +1,271 @@ + + + + + Arroyo Architecture - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Arroyo Architecture

+

Arroyo is a set of high level abstractions to interact with Kafka. +These are meant to help the developer in writing performant consumers with +specific delivery guarantees.

+

Common problems addressed by Arroyo are guaranteeing at-least-once delivery, +providing a dead letter queue abstraction, support parallel (multi-processing) +message processing, etc.

+

The library is divided into three layers: the basic Kafka connectivity, the +streaming engine and the high level abstractions.

+

The basic connectivity layer is a simple wrapper around the Confluent python +library, which is itself based on librdkafka. Besides some cosmetic changes, +this level provides a Fake in memory broker and consumer to make unit test quick +to run.

+

The streaming engine provides an asynchronous processing interface to write +consumers. The consumer is written as a pipeline where each segment is an +asynchronous operation. The streaming engine implements the main consumer loop +and delegates the processing to the pipeline.

+

On top of the streaming engine, the library provides high-level abstractions that +are common when writing Kafka consumers like: map, reduce, filter together +with some common messaging application patterns like the dead letter queue.

+
+

Streaming Interface and Streaming Engine

+

A Kafka consumer is built as a pipeline where each segment processes messages in +an asynchronous way. The Streaming engine provides a message to a segment. The +segment is not supposed to execute small CPU work in a blocking way or do IO in a +non-blocking way. We generally use futures for this, and heavier CPU work in a +separate process.

+

Arroyo provides an interface to implement to write a pipeline segment. +The segment interface is called ProcessingStrategy and is in +this module. +(TODO: bring the docstrings to the docs and reference that).

+

In most cases, when developing a consumer, the developer would not implement +that interface directly. A higher level abstraction would be used.

+
+_images/arroyo_processing.png +
+

The main consumer loop is managed by the stream engine. +These are the phases:

+
    +
  • Poll from the Kafka consumer through the basic library. If a message is there +proceed or repeat.

  • +
  • Submit the message to the first ProcessingStrategy. This is supposed to deliver +work for the strategy to do. It is not supposed to be a blocking operation. The +strategy should return immediately.

  • +
  • Poll the strategy to execute work or to forward results to the following step +in the pipeline. Ideally all IO should be done in separate threads and heavy cpu +work should be done in separate processes so the poll method should check for +completed work, dispatch to the next step and return. In practice, work is executed +here in a blocking way if the overhead of offloading the work is too high.

  • +
+

The ProcessingStrategy may decide not to take the message and instead apply back-pressure. +This is done by raising the MessageRejected exception. In this case, the streaming +engine pauses the consumer till the strategy is ready to accept the message.

+

The ProcessingStrategy decides when it is time to commit a message. This is done +through a commit callback provided to the strategy when it is instantiated.

+

The streaming engine orchestrates the life cycle of the ProcessingStrategy, thus +when it thinks it is time to shut the strategy down it would wait for all in-flight +work to be completed and then destroy the strategy.

+

There are two scenarios where this can happen:

+
    +
  • The consumer is being terminated.

  • +
  • A rebalancing happened. A rebalancing revokes partitions and assigns new ones. +After a rebalancing is complete it is impossible to commit a message from a partition +that was revoked. In order to ensure the consumer behaves in a consistent way, +upon rebalancing, the streaming engine destroys the strategy and builds a new one. +This allows the strategy to complete all in-flight work before being terminated.

  • +
+
+
+

High level strategies

+

Most consumers follow the same few patterns, so Arroyo provides abstractions that +are based on the ProcessingStrategy but are simpler to implement for the common +use cases.

+

Common examples are:

+
    +
  • run task, run task in threads, run task with multiprocessing. The run task +set of strategies are designed to be the most flexible and simple to use. They take +a function provided by the user and execute it on every message, passing the output +to the next step. The library includes synchronous and asynchronous versions depending +on the kind of concurrency required by the user.

  • +
  • filter, map and forward. This type of consumer inspects a message, decides +whether to process it or discard it, transforms its content, and produces the result +on a new topic. In this case, Arroyo provides three implementations of the +ProcessingStrategy: filter, transform, and produce. The developer only needs +to wire them together and provide the map and filtering logic.

  • +
  • consume, apply side effects, produce. This is a variation of the one above. +In this case, the transform operation can have side-effects like storing the content +of the message somewhere.

  • +
  • high throughput cpu intensive transform. The python GIL does not allow CPU intensive +work to take advantage of parallelism. Arroyo provides an implementation of the map +pattern that batches messages and dispatches the work to separate processes via shared +memory. This is largely transparent to the developers.

  • +
  • map, reduce and store. The reduce function is carried out by the Collector, which +batches messages and executes some logic with side-effects when the batch is full. +This is a typical way to write messages on a storages in batches to reduce the +round trips.

  • +
+

All strategies included with Arroyo are in the strategies module.

+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/backpressure.html b/backpressure.html new file mode 100644 index 00000000..62964899 --- /dev/null +++ b/backpressure.html @@ -0,0 +1,203 @@ + + + + + Backpressure - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Backpressure

+

Arroyo’s own processing strategies internally apply backpressure by raising +MessageRejected. Most +consumers do not require additional work to deal with backpressure correctly.

+

If you want to slow down the consumer based on some external signal or +condition, you can achieve that most effectively by raising the same exception +from within a callback passed to RunTask while the +consumer is supposed to be paused

+
class ConsumerStrategyFactory(ProcessingStrategyFactory[KafkaPayload]):
+    def __init__(self):
+        self.is_paused = False
+
+    def create_with_partitions(
+        self,
+        commit: Commit,
+        partitions: Mapping[Partition, int],
+    ) -> ProcessingStrategy[KafkaPayload]:
+        def handle_message(message: Message[KafkaPayload]) -> Message[KafkaPayload]:
+            if self.is_paused:
+                raise MessageRejected()
+
+            print(f"MSG: {message.payload}")
+            return message
+
+        return RunTask(handle_message, CommitOffsets(commit))
+
+
+

It is not recommended to apply backpressure by just sleep()-ing in +submit (or, in this example, +handle_message) for more than a few milliseconds. While this definitely +pauses the consumer, it will block the main thread for too long and and prevent +things like consumer rebalancing from occuring.

+

A 0.01 second sleep is applied each time MessageRejected is +raised to prevent the main thread spinning at 100% CPU. However background thread +performance may be impacted during this time.

+
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/dlqs.html b/dlqs.html new file mode 100644 index 00000000..2ce333a7 --- /dev/null +++ b/dlqs.html @@ -0,0 +1,233 @@ + + + + + Dead letter queues - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Dead letter queues

+
+

Warning

+

Dead letter queues should be used with caution as they break some of the ordering guarantees +otherwise offered by Arroyo and Kafka consumer code. In particular, it must be safe for the +consumer to drop a message. If replaying or later re-processing of the DLQ’ed messages is done, +it is critical that ordering is not a requirement in the relevant downstream code.

+
+

Arroyo provides support for routing invalid messages to dead letter queues in consumers. +Dead letter queues are critical in some applications because messages are ordered in Kafka +and a single invalid message can cause a consumer to crash and every subsequent message to +not be processed.

+

The dead letter queue configuration is passed to the StreamProcessor and, if provided, any +InvalidMessage raise by a strategy will be produced to the dead letter queue.

+
+
+class arroyo.dlq.DlqLimit(max_invalid_ratio: float | None = None, max_consecutive_count: int | None = None)
+

Defines any limits that should be placed on the number of messages that are +forwarded to the DLQ. This exists to prevent 100% of messages from going into +the DLQ if something is misconfigured or bad code is deployed. In this scenario, +it may be preferable to stop processing messages altogether and deploy a fix +rather than rerouting every message to the DLQ.

+

The ratio and max_consecutive_count are counted on a per-partition basis.

+
+ +
+
+class arroyo.dlq.DlqPolicy(producer: DlqProducer[TStrategyPayload], limit: DlqLimit | None = None, max_buffered_messages_per_partition: int | None = None)
+

DLQ policy defines the DLQ configuration, and is passed to the stream processor +upon creation of the consumer. It consists of the DLQ producer implementation and +any limits that should be applied.

+
+ +
+
+exception arroyo.dlq.InvalidMessage(partition: Partition, offset: int, needs_commit: bool = True)
+

InvalidMessage should be raised if a message is not valid for processing and +should not be retried. It will be placed a DLQ if one is configured.

+

It can be raised from the submit, poll or join methods of any processing strategy.

+

Once a filtered message is forwarded to the next step, needs_commit should be set to False, +in order to prevent multiple filtered messages from being forwarded for a single invalid message.

+
+ +
+
+class arroyo.dlq.KafkaDlqProducer(producer: Producer[KafkaPayload], topic: Topic)
+

KafkaDLQProducer forwards invalid messages to a Kafka topic

+

Two additional fields are added to the headers of the Kafka message +“original_partition”: The partition of the original message +“original_offset”: The offset of the original message

+
+ +
+
+class arroyo.dlq.NoopDlqProducer(*args, **kwds)
+

Drops all invalid messages

+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/genindex.html b/genindex.html new file mode 100644 index 00000000..15713a1d --- /dev/null +++ b/genindex.html @@ -0,0 +1,532 @@ + + + + + Index - Arroyo documentation + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+
+ + +

Index

+ +
+ A + | B + | C + | D + | F + | G + | H + | I + | J + | K + | M + | N + | O + | P + | R + | S + | T + | U + | V + +
+

A

+ + + +
    +
  • + arroyo.dlq + +
  • +
  • + arroyo.processing.strategies.abstract + +
  • +
  • + arroyo.processing.strategies.batching + +
  • +
  • + arroyo.processing.strategies.commit + +
  • +
  • + arroyo.processing.strategies.filter + +
  • +
  • + arroyo.processing.strategies.healthcheck + +
  • +
  • + arroyo.processing.strategies.noop + +
  • +
  • + arroyo.processing.strategies.produce + +
  • +
    +
  • + arroyo.processing.strategies.reduce + +
  • +
  • + arroyo.processing.strategies.run_task + +
  • +
  • + arroyo.processing.strategies.run_task_in_threads + +
  • +
  • + arroyo.processing.strategies.run_task_with_multiprocessing + +
  • +
  • + arroyo.processing.strategies.unfold + +
  • +
  • + arroyo.types + +
  • +
  • + arroyo.utils.metrics + +
  • +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

F

+ + + +
+ +

G

+ + +
+ +

H

+ + +
+ +

I

+ + + +
+ +

J

+ + +
+ +

K

+ + +
+ +

M

+ + +
+ +

N

+ + + +
+ +

O

+ + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ + + +
+ +
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/getstarted.html b/getstarted.html new file mode 100644 index 00000000..af0e6014 --- /dev/null +++ b/getstarted.html @@ -0,0 +1,397 @@ + + + + + Getting started with Arroyo - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Getting started with Arroyo

+

This tutorial shows how to create a Kafka consumer with Arroyo from scratch.

+
+

Setup

+

This section explains how to setup Kafka, Zookeeper and install the library

+
+

Kafka and Zookeeper

+

In order to run an arroyo Kafka consumer you will need a working Kafka broker. +If you already have one, you can skip this step. +If you do not have a running Kafka broker, this command will install and start +a Kafka docker container. (It requires Docker to be installed).

+
docker network create arroyo
+
+docker run --rm \
+    -v zookeeper_volume:/var/lib/zookeeper \
+    --env ZOOKEEPER_CLIENT_PORT=2181 \
+    --name=sentry_zookeeper \
+    --network=arroyo \
+    -p 2181:2181 \
+    confluentinc/cp-zookeeper:6.2.0
+
+docker run --rm \
+    -v kafka_volume:/var/lib/kafka \
+    --env KAFKA_ZOOKEEPER_CONNECT=sentry_zookeeper:2181 \
+    --env KAFKA_LISTENERS=INTERNAL://0.0.0.0:9093,EXTERNAL://0.0.0.0:9092 \
+    --env KAFKA_ADVERTISED_LISTENERS=INTERNAL://127.0.0.1:9093,EXTERNAL://127.0.0.1:9092 \
+    --env KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT \
+    --env KAFKA_INTER_BROKER_LISTENER_NAME=INTERNAL \
+    --env KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
+    --env CONFLUENT_SUPPORT_METRICS_ENABLE=false \
+    --env KAFKA_LOG4J_LOGGERS=kafka.cluster=WARN,kafka.controller=WARN,kafka.coordinator=WARN,kafka.log=WARN,kafka.server=WARN,kafka.zookeeper=WARN,state.change.logger=WARN \
+    --env KAFKA_LOG4J_ROOT_LOGLEVEL=WARN \
+    --env KAFKA_TOOLS_LOG4J_LOGLEVEL=WARN \
+    --name=sentry_kafka \
+    --network=arroyo \
+    -p 9092:9092 \
+    confluentinc/cp-kafka:6.2.0
+
+
+

Now you should see Kafka and Zookeeper running with

+
docker ps
+
+
+
+
+

Install Kafkacat

+

This tool will be useful to produce onto and consume from topics.

+
https://docs.confluent.io/platform/current/app-development/kafkacat-usage.html#kcat-formerly-kafkacat-utility
+
+
+
+
+

Development environment

+

You will need to install the library. Most likely in a python venv. So first, create a python virtual +environment. Then you can install arroyo with this.

+
pip install sentry-arroyo
+
+
+
+
+

Create two Kafka topics

+

Our example will consume from one topic and produce the same messages on another topic. So we need +two topics.

+
docker exec sentry_kafka kafka-topics \
+    --create \
+    --topic source-topic \
+    --bootstrap-server 127.0.0.1:9092
+
+docker exec sentry_kafka kafka-topics \
+    --create \
+    --topic dest-topic \
+    --bootstrap-server 127.0.0.1:9092
+
+
+

Now you should be ready to develop with Arroyo.

+
+
+
+

Create a basic consumer

+

Arroyo provides two level of abstractions when writing a consumer: the basic consumer/producer library +and the Streaming library. The first is just a thin wrapper around a librdkafka consumer/producer that +adds some features around offset management. The second provides a more abstract streaming interface +that hides details like rebalancing and the consumer lifecycle.

+
+

Creating a basic consumer

+

This initializes a basic consumer and consumes a message.

+
from arroyo.backends.kafka.configuration import (
+    build_kafka_consumer_configuration,
+)
+from arroyo.backends.kafka.consumer import KafkaConsumer
+from arroyo.types import Topic
+
+TOPIC = Topic("source-topic")
+
+consumer = KafkaConsumer(
+    build_kafka_consumer_configuration(
+        default_config={},
+        bootstrap_servers=["127.0.0.1:9092"],
+        auto_offset_reset="latest",
+        group_id="test-group",
+    )
+)
+
+consumer.subscribe([TOPIC])
+
+while True:
+    msg = consumer.poll(timeout=1.0)
+    if msg is not None:
+        print(f"MSG: {msg.payload}")
+
+
+

Start this script and use kcat to produce a message:

+
echo "MESSAGE" | kcat -P -b 127.0.0.1:9092 -t source-topic
+
+
+

In a while the message should appear on the console:

+
MSG: KafkaPayload(key=None, value=b'MESSAGE', headers=[])
+
+
+
+
+

Create a streaming consumer

+

Add a ProcessingStrategy and ProcessingStrategyFactory. +Here we are using the RunTask strategy which runs a custom function over each message.

+
from typing import Mapping
+
+from arroyo.backends.kafka import KafkaPayload
+from arroyo.processing.strategies import (
+    CommitOffsets,
+    ProcessingStrategy,
+    ProcessingStrategyFactory,
+    RunTask,
+)
+from arroyo.types import Commit, Message, Partition, Topic
+
+
+def handle_message(message: Message[KafkaPayload]) -> Message[KafkaPayload]:
+    print(f"MSG: {message.payload}")
+    return message.payload
+
+class ConsumerStrategyFactory(ProcessingStrategyFactory[KafkaPayload]):
+    """
+    The factory manages the lifecycle of the `ProcessingStrategy`.
+    A strategy is created every time new partitions are assigned to the
+    consumer, while it is destroyed when partitions are revoked or the
+    consumer is closed
+    """
+    def create_with_partitions(
+        self,
+        commit: Commit,
+        partitions: Mapping[Partition, int],
+    ) -> ProcessingStrategy[KafkaPayload]:
+        return RunTask(handle_message, CommitOffsets(commit))
+
+
+

The code above is orchestrated by the Arroyo runtime called StreamProcessor.

+
from arroyo.processing import StreamProcessor
+from arroyo.commit import ONCE_PER_SECOND
+
+processor = StreamProcessor(
+    consumer=consumer,
+    topic=TOPIC,
+    processor_factory=ConsumerStrategyFactory(),
+    commit_policy=ONCE_PER_SECOND,
+)
+
+processor.run()
+
+
+

The main consumer loop is managed by the StreamProcessor no need to periodically poll the +consumer. The ConsumerStrategy works by inversion of control.

+
+
+

Add some useful logic

+

Now we will chain the Produce strategy to produce messages on a second topic after the message is logged

+
from arroyo.backends.kafka import KafkaProducer
+from arroyo.backends.kafka.configuration import build_kafka_configuration
+from arroyo.processing.strategies import Produce
+
+class ConsumerStrategyFactory(ProcessingStrategyFactory[KafkaPayload]):
+    """
+    The factory manages the lifecycle of the `ProcessingStrategy`.
+    A strategy is created every time new partitions are assigned to the
+    consumer, while it is destroyed when partitions are revoked or the
+    consumer is closed
+    """
+    def create_with_partitions(
+        self,
+        commit: Commit,
+        partitions: Mapping[Partition, int],
+    ) -> ProcessingStrategy[KafkaPayload]:
+        producer = KafkaProducer(
+            build_kafka_configuration(
+                default_config={},
+                bootstrap_servers=BOOTSTRAP_SERVERS,
+            )
+        )
+
+        return RunTask(
+            handle_message,
+            Produce(producer, Topic("dest-topic"), CommitOffsets(commit))
+        )
+
+
+

The message is first passed to the RunTask strategy which simply logs the message and submits +the output to the next step. The Produce strategy produces the message asynchronously. Once +the message is produced, the CommitOffsets strategy commits the offset of the message.

+
+
+
+

Further examples

+

Find some complete examples of usage.

+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..b7f1c963 --- /dev/null +++ b/index.html @@ -0,0 +1,186 @@ + + + + + Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+ _images/arroyo-banner.png +

Arroyo is a library to build streaming applications that consume from +and produce to Kafka.

+

It relies on the confluent_kafka python library, which itself relies +on librdkafka.

+

Arroyo provides mainly three functionalities:

+
    +
  • A set of abstractions inspired to common messaging applications patterns.

  • +
  • Some abstractions to simplify offset management and rebalancing.

  • +
  • An in memory broker abstraction to simplify writing unit tests.

  • +
+
+

Contents:

+ +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/intro.html b/intro.html new file mode 100644 index 00000000..16d58a53 --- /dev/null +++ b/intro.html @@ -0,0 +1,163 @@ + + + + + <no title> - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+ _images/arroyo-banner.png +

Arroyo is a library to build streaming applications that consume from +and produce to Kafka.

+

It relies on the confluent_kafka python library, which itself relies +on librdkafka.

+

Arroyo provides mainly three functionalities:

+
    +
  • A set of abstractions inspired to common messaging applications patterns.

  • +
  • Some abstractions to simplify offset management and rebalancing.

  • +
  • An in memory broker abstraction to simplify writing unit tests.

  • +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/metrics.html b/metrics.html new file mode 100644 index 00000000..a7f9926d --- /dev/null +++ b/metrics.html @@ -0,0 +1,354 @@ + + + + + Metrics - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Metrics

+

Arroyo consumers and strategies attempt to auto instrument some metrics that most people find useful +to understand the behavior and performance of their consumers. These metrics are typically sampled or +buffered as appropriate and flushed periodically (often once per second).

+

In order to use these metrics, you must configure a metrics backend that conforms to the metrics protocol +before creating your consumer.

+

This can be done like so:

+
from arroyo.utils.metrics import Metrics, MetricName
+
+ class MyMetrics(Metrics):
+     def increment(
+         self, name: MetricName, value: Union[int, float] = 1, tags: Optional[Tags] = None
+     ) -> None:
+         # Increment a counter by the given value.
+         record_incr(name, value, tags)
+
+     def gauge(
+         self, name: MetricName, value: Union[int, float], tags: Optional[Tags] = None
+     ) -> None:
+         # Sets a gauge metric to the given value.
+         record_gauge(name, value, tags)
+
+     def timing(
+         self, name: MetricName, value: Union[int, float], tags: Optional[Tags] = None
+     ) -> None:
+         # Emit a timing metric with the given value.
+         record_timing(name, value, tags)
+
+ metrics_backend = MyMetrics()
+
+ configure_metrics(metrics_backend)
+
+
+
+

Available Metrics

+
from typing import Literal
+
+MetricName = Literal[
+    # Time: Number of messages in a multiprocessing batch
+    "arroyo.strategies.run_task_with_multiprocessing.batch.size.msg",
+    # Time: Number of bytes in a multiprocessing batch
+    "arroyo.strategies.run_task_with_multiprocessing.batch.size.bytes",
+    # Time: Number of messages in a multiprocessing batch after the message transformation
+    "arroyo.strategies.run_task_with_multiprocessing.output_batch.size.msg",
+    # Time: Number of bytes in a multiprocessing batch after the message transformation
+    "arroyo.strategies.run_task_with_multiprocessing.output_batch.size.bytes",
+    # Counter: Number of times the consumer is spinning
+    "arroyo.consumer.run.count",
+    # Counter: Number of times the consumer encountered an invalid message.
+    "arroyo.consumer.invalid_message.count",
+    # Time: How long it took the Reduce step to fill up a batch
+    "arroyo.strategies.reduce.batch_time",
+    # Counter: Incremented when a strategy after multiprocessing applies
+    # backpressure to multiprocessing. May be a reason why CPU cannot be
+    # saturated.
+    "arroyo.strategies.run_task_with_multiprocessing.batch.backpressure",
+    # Counter: Incremented when multiprocessing cannot fill the input batch
+    # because not enough memory was allocated. This results in batches smaller
+    # than configured. Increase `input_block_size` to fix.
+    "arroyo.strategies.run_task_with_multiprocessing.batch.input.overflow",
+    # Counter: Incremented when multiprocessing cannot pull results in batches
+    # equal to the input batch size, because not enough memory was allocated.
+    # This can be devastating for throughput. Increase `output_block_size` to
+    # fix.
+    "arroyo.strategies.run_task_with_multiprocessing.batch.output.overflow",
+    # Counter: Arroyo has decided to re-allocate a block in order to combat input
+    # buffer overflow. This behavior can be disabled by explicitly setting
+    # `input_block_size` to a not-None value in `RunTaskWithMultiprocessing`.
+    "arroyo.strategies.run_task_with_multiprocessing.batch.input.resize",
+    # Counter: Arroyo has decided to re-allocate a block in order to combat output
+    # buffer overflow. This behavior can be disabled by explicitly setting
+    # `output_block_size` to a not-None value in `RunTaskWithMultiprocessing`.
+    "arroyo.strategies.run_task_with_multiprocessing.batch.output.resize",
+    # Gauge: How many batches are being processed in parallel by multiprocessing.
+    "arroyo.strategies.run_task_with_multiprocessing.batches_in_progress",
+    # Counter: A subprocess by multiprocessing unexpectedly died.
+    "sigchld.detected",
+    # Gauge: Shows how many processes the multiprocessing strategy is
+    # configured with.
+    "arroyo.strategies.run_task_with_multiprocessing.processes",
+    # Counter: Incremented when the multiprocessing pool is created (or re-created).
+    "arroyo.strategies.run_task_with_multiprocessing.pool.create",
+    # Time: (unitless) spent polling librdkafka for new messages.
+    "arroyo.consumer.poll.time",
+    # Time: (unitless) spent in strategies (blocking in strategy.submit or
+    # strategy.poll)
+    "arroyo.consumer.processing.time",
+    # Time: (unitless) spent pausing the consumer due to backpressure (MessageRejected)
+    "arroyo.consumer.backpressure.time",
+    # Time: (unitless) spent in handling `InvalidMessage` exceptions and sending
+    # messages to the the DLQ.
+    "arroyo.consumer.dlq.time",
+    # Time: (unitless) spent in waiting for the strategy to exit, such as during
+    # shutdown or rebalancing.
+    "arroyo.consumer.join.time",
+    # Time: (unitless) spent in librdkafka callbacks. This metric's timings
+    # overlap other timings, and might spike at the same time.
+    "arroyo.consumer.callback.time",
+    # Time: (unitless) spent in shutting down the consumer. This metric's
+    # timings overlap other timings, and might spike at the same time.
+    "arroyo.consumer.shutdown.time",
+    # Time: A regular duration metric where each datapoint is measuring the time it
+    # took to execute a single callback. This metric is distinct from the
+    # arroyo.consumer.*.time metrics as it does not attempt to accumulate time
+    # spent per second in an attempt to keep monitoring overhead low.
+    #
+    # The metric is tagged by the name of the internal callback function being
+    # executed, as 'callback_name'. Possible values are on_partitions_assigned
+    # and on_partitions_revoked.
+    "arroyo.consumer.run.callback",
+    # Time: Duration metric measuring the time it took to flush in-flight messages
+    # and shut down the strategies.
+    "arroyo.consumer.run.close_strategy",
+    # Time: Duration metric measuring the time it took to create the processing strategy.
+    "arroyo.consumer.run.create_strategy",
+    # Counter: How many partitions have been revoked just now.
+    "arroyo.consumer.partitions_revoked.count",
+    # Counter: How many partitions have been assigned just now.
+    "arroyo.consumer.partitions_assigned.count",
+    # Time: Consumer latency in seconds. Recorded by the commit offsets strategy.
+    "arroyo.consumer.latency",
+    # Counter: Metric for when the underlying rdkafka consumer is being paused.
+    #
+    # This flushes internal prefetch buffers.
+    "arroyo.consumer.pause",
+    # Counter: Metric for when the underlying rdkafka consumer is being resumed.
+    #
+    # This might cause increased network usage as messages are being re-fetched.
+    "arroyo.consumer.resume",
+    # Gauge: Queue size of background queue that librdkafka uses to prefetch messages.
+    "arroyo.consumer.librdkafka.total_queue_size",
+    # Counter: Counter metric to measure how often the healthcheck file has been touched.
+    "arroyo.processing.strategies.healthcheck.touch",
+    # Counter: Number of messages dropped in the FilterStep strategy
+    "arroyo.strategies.filter.dropped_messages",
+]
+
+
+

For convenience Arroyo includes a machine readable version which can be loaded like:

+
import importlib.resources
+import json
+
+with importlib.resources.files("arroyo.utils").joinpath("metricDefs.json").open() as f:
+    metric_defs = json.load(f)
+
+
+
+
+

API

+
+
+class arroyo.utils.metrics.Metrics(*args, **kwargs)
+

An abstract class that defines the interface for metrics backends.

+
+
+abstract gauge(name: MetricName, value: int | float, tags: Mapping[str, str] | None = None) None
+

Sets a gauge metric to the given value.

+
+ +
+
+abstract increment(name: MetricName, value: int | float = 1, tags: Mapping[str, str] | None = None) None
+

Increments a counter metric by a given value.

+
+ +
+
+abstract timing(name: MetricName, value: int | float, tags: Mapping[str, str] | None = None) None
+

Records a timing metric.

+
+ +
+ +
+
+arroyo.utils.metrics.configure_metrics(metrics: Metrics, force: bool = False) None
+

Metrics can generally only be configured once, unless force is passed +on subsequent initializations.

+
+ +
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 00000000..35b2a96d Binary files /dev/null and b/objects.inv differ diff --git a/offsets.html b/offsets.html new file mode 100644 index 00000000..313783cc --- /dev/null +++ b/offsets.html @@ -0,0 +1,197 @@ + + + + + Committing offsets - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Committing offsets

+

Arroyo does not auto commit offsets. It is up to you to manually commit offsets when processing for that +message is completed.

+

The commit callback will be passed to processing strategy via ProcessingStrategyFactory.create_with_partitions. +You should pass this to the strategy and have your strategy call this commit function once the rest of the message +processing has been done.

+

The offset to be committed in Kafka is always the next offset to be consumed from, i.e. message’s offset + 1. +In Arroyo, this means you should commit Message.next_offset and never Message.offset when done processing +that message. Arroyo exposes Message.position_to_commit to make this easier.

+

It is not safe to commit every offset in a high throughput consumer as this will add a lot of load to the system. +Commits should generally be throttled. CommitPolicy is the Arroyo way of specifying commit frequency. A CommitPolicy +must be passed to the stream processor, which allows specifying a minimum commit frequency (or messages between commits). +Commit throttling can be skipped when needed (i.e. during consumer shutdown) by passing force=True to the commit callback. +If you are not sure how often to commit, ONCE_PER_SECOND is a reasonable option.

+

The easiest way is to use the CommitOffsets strategy as the last step in a chain of processing strategies to commit offsets.

+
class MyConsumerFactoryFactory(ProcessingStrategyFactory[KafkaPayload]):
+    def create_with_partitions(
+        self,
+        commit: Commit,
+        partitions: Mapping[Partition, int],
+    ) -> ProcessingStrategy[KafkaPayload]:
+        def my_processing_function(message: Message[KafkaPayload]) -> None:
+            # do something (synchronous) with the message
+            do_something()
+
+
+        return RunTask(my_processing_function, CommitOffsets(commit))
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/py-modindex.html b/py-modindex.html new file mode 100644 index 00000000..1e42bf92 --- /dev/null +++ b/py-modindex.html @@ -0,0 +1,179 @@ + + + + + Python Module Index - Arroyo documentation + + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+
+ + +

Python Module Index

+ +
+ a +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ a
+ arroyo +
    + arroyo.dlq +
    + arroyo.processing.strategies.abstract +
    + arroyo.processing.strategies.batching +
    + arroyo.processing.strategies.commit +
    + arroyo.processing.strategies.filter +
    + arroyo.processing.strategies.healthcheck +
    + arroyo.processing.strategies.noop +
    + arroyo.processing.strategies.produce +
    + arroyo.processing.strategies.reduce +
    + arroyo.processing.strategies.run_task +
    + arroyo.processing.strategies.run_task_in_threads +
    + arroyo.processing.strategies.run_task_with_multiprocessing +
    + arroyo.processing.strategies.unfold +
    + arroyo.types +
    + arroyo.utils.metrics +
+ + +
+ +
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/search.html b/search.html new file mode 100644 index 00000000..c8cdc800 --- /dev/null +++ b/search.html @@ -0,0 +1,115 @@ + + + + + Search - Arroyo documentation + + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+
+ +

Search

+ + + + +

+ Searching for multiple words only shows matches that contain + all words. +

+ + +
+ + +
+

+ + + +
+ +
+ + +
+ +
+
+
+
+ +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 00000000..2e873082 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["architecture", "backpressure", "dlqs", "getstarted", "index", "intro", "metrics", "offsets", "strategies/batching", "strategies/commit_offsets", "strategies/filter", "strategies/healthcheck", "strategies/index", "strategies/noop", "strategies/produce", "strategies/reduce", "strategies/run_task", "strategies/run_task_in_threads", "strategies/run_task_with_multiprocessing", "strategies/unfold", "what_for"], "filenames": ["architecture.rst", "backpressure.rst", "dlqs.rst", "getstarted.rst", "index.rst", "intro.rst", "metrics.rst", "offsets.rst", "strategies/batching.rst", "strategies/commit_offsets.rst", "strategies/filter.rst", "strategies/healthcheck.rst", "strategies/index.rst", "strategies/noop.rst", "strategies/produce.rst", "strategies/reduce.rst", "strategies/run_task.rst", "strategies/run_task_in_threads.rst", "strategies/run_task_with_multiprocessing.rst", "strategies/unfold.rst", "what_for.rst"], "titles": ["Arroyo Architecture", "Backpressure", "Dead letter queues", "Getting started with Arroyo", "Contents:", "<no title>", "Metrics", "Committing offsets", "Batch and Unbatch", "Commit offsets", "Filter", "Healthchecks", "Processing Strategies", "Noop", "Produce", "Reduce (Fold)", "Run Task", "Run Task in Threads", "Run Task with Multiprocessing", "Unfold", "What is Arroyo for?"], "terms": {"i": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19], "set": [0, 2, 4, 5, 6, 11, 18], "abstract": [0, 3, 4, 5, 6, 12, 20], "interact": 0, "kafka": [0, 2, 4, 5, 7, 11, 12, 14], "These": [0, 6], "ar": [0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 14, 15, 17, 18, 19, 20], "meant": 0, "help": [0, 20], "develop": 0, "write": [0, 3, 4, 5, 12, 20], "perform": [0, 1, 6, 12, 18, 20], "consum": [0, 1, 2, 4, 5, 6, 7, 9, 10, 11, 12, 14], "specif": 0, "deliveri": 0, "guarante": [0, 2], "common": [0, 4, 5], "problem": 0, "address": 0, "least": [0, 18], "onc": [0, 2, 3, 6, 7, 9, 11, 12, 15, 17, 18, 20], "provid": [0, 2, 3, 4, 5, 8, 12, 16, 19], "dead": [0, 4, 16], "letter": [0, 4, 16], "queue": [0, 4, 6, 16, 20], "support": [0, 2, 11], "parallel": [0, 6, 17, 18], "multi": 0, "process": [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 19, 20], "messag": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20], "etc": [0, 12], "The": [0, 2, 3, 6, 7, 8, 11, 12, 14, 15, 16, 17, 18, 19, 20], "librari": [0, 3, 4, 5, 20], "divid": [0, 20], "three": [0, 4, 5], "layer": 0, "basic": [0, 10, 16], "connect": 0, "simpl": 0, "wrapper": [0, 3], "around": [0, 3], "confluent": [0, 3], "python": [0, 3, 4, 5], "which": [0, 3, 4, 5, 6, 7, 8, 14, 17, 20], "itself": [0, 4, 5, 14, 18], "base": [0, 1, 8, 12, 15, 19], "librdkafka": [0, 3, 4, 5, 6], "besid": 0, "some": [0, 1, 2, 4, 5, 6, 14, 18], "cosmet": 0, "chang": [0, 3], "thi": [0, 1, 2, 3, 6, 7, 8, 9, 10, 11, 12, 14, 15, 17, 18, 19, 20], "fake": 0, "memori": [0, 4, 5, 6, 18], "broker": [0, 3, 4, 5, 9, 11, 12, 18, 20], "make": [0, 7, 10, 18, 20], "unit": [0, 4, 5], "test": [0, 3, 4, 5, 11, 18, 20], "quick": 0, "run": [0, 3, 6, 11, 12, 15, 20], "an": [0, 3, 4, 5, 6, 14, 17, 18, 19, 20], "asynchron": [0, 3, 12], "written": [0, 12], "pipelin": [0, 11], "where": [0, 6, 14, 18], "each": [0, 1, 3, 6, 12, 14, 15, 17, 18, 20], "segment": 0, "oper": [0, 20], "implement": [0, 2, 12, 19], "main": [0, 1, 3, 11, 18], "loop": [0, 3, 12], "deleg": 0, "On": [0, 14, 17], "top": [0, 20], "when": [0, 3, 6, 7, 8, 12, 18], "like": [0, 1, 3, 6, 11, 18, 20], "map": [0, 1, 3, 6, 7, 11, 12], "reduc": [0, 6, 8, 12, 18, 20], "filter": [0, 2, 6], "togeth": [0, 8, 12, 15], "applic": [0, 2, 4, 5, 12, 20], "pattern": [0, 4, 5], "A": [0, 1, 3, 4, 5, 6, 7, 8, 11, 12, 14, 20], "built": [0, 12, 20], "wai": [0, 7, 20], "suppos": [0, 1], "execut": [0, 6, 12], "small": [0, 18], "cpu": [0, 1, 6, 18], "work": [0, 1, 3, 12, 18, 20], "block": [0, 1, 6, 11, 12, 18, 20], "do": [0, 1, 3, 7, 10, 11, 18, 20], "io": [0, 3, 11, 17, 20], "non": 0, "we": [0, 3, 8, 12, 14, 17, 20], "gener": [0, 6, 7, 8, 12, 18, 19], "us": [0, 2, 6, 7, 8, 9, 12, 14, 17, 18, 20], "futur": [0, 14, 17], "heavier": 0, "separ": [0, 20], "call": [0, 3, 7, 8, 11, 12, 19, 20], "processingstrategi": [0, 1, 3, 7, 8, 10, 11, 12, 14, 15, 16, 17, 18, 19], "modul": [0, 18], "todo": [0, 20], "bring": 0, "docstr": 0, "doc": [0, 3, 20], "refer": [0, 14], "In": [0, 2, 3, 6, 7, 18, 20], "most": [0, 1, 3, 6, 11, 12, 18, 20], "case": [0, 14, 18], "would": [0, 8, 11, 20], "directli": 0, "higher": [0, 11, 20], "manag": [0, 3, 4, 5], "phase": 0, "poll": [0, 2, 3, 6, 8, 11, 12, 14, 17, 19, 20], "from": [0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 14, 15, 18, 19, 20], "through": 0, "If": [0, 1, 2, 3, 7, 8, 10, 11, 12, 14, 17, 18, 19, 20], "proce": 0, "repeat": 0, "submit": [0, 1, 2, 3, 6, 8, 10, 12, 14, 15, 17, 19], "first": [0, 3, 8, 18], "deliv": 0, "It": [0, 1, 2, 3, 4, 5, 7, 9, 12], "should": [0, 2, 3, 7, 8, 9, 10, 11, 12, 15, 16, 18, 20], "return": [0, 1, 3, 7, 10, 11, 12, 19, 20], "immedi": [0, 12, 20], "forward": [0, 2, 10, 18], "result": [0, 6, 10, 17, 18], "follow": [0, 8, 12, 18, 20], "step": [0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 14, 15, 17, 18, 19], "ideal": 0, "all": [0, 2, 8, 9, 12, 18, 20], "done": [0, 2, 6, 7, 12, 17], "thread": [0, 1, 11, 12, 14], "heavi": 0, "so": [0, 3, 6, 8, 10, 11, 12, 18, 20], "method": [0, 2, 8, 12, 14, 17], "check": [0, 12, 14, 17], "complet": [0, 3, 7, 9, 12, 14, 17], "dispatch": 0, "next": [0, 2, 3, 7, 8, 10, 14, 15, 17, 18, 19, 20], "practic": 0, "here": [0, 3, 20], "overhead": [0, 6], "offload": 0, "too": [0, 1, 11, 14, 17, 18], "mai": [0, 1, 2, 6, 10, 12, 18], "decid": [0, 6, 20], "take": [0, 10, 11, 13, 17, 18], "instead": [0, 8, 10, 18], "appli": [0, 1, 2, 6, 14, 18], "back": [0, 9, 18], "pressur": 0, "rais": [0, 1, 2, 12, 14, 16, 17], "messagereject": [0, 1, 6, 8, 12, 14, 15, 17, 19], "except": [0, 1, 2, 6, 8, 12, 14, 15, 17, 19, 20], "paus": [0, 1, 6], "till": 0, "readi": [0, 3, 8], "accept": [0, 12], "time": [0, 1, 3, 6, 8, 10, 11, 12, 15, 18, 20], "commit": [0, 1, 3, 4, 6, 10, 11, 12], "callback": [0, 1, 6, 7, 10], "instanti": [0, 12], "orchestr": [0, 3], "life": 0, "cycl": 0, "thu": [0, 8], "think": 0, "shut": [0, 6, 11, 12], "down": [0, 1, 6, 11, 12, 14, 17], "wait": [0, 6, 18], "flight": [0, 6], "destroi": [0, 3], "There": [0, 20], "two": [0, 2, 18], "scenario": [0, 2, 10, 20], "can": [0, 1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 16, 17, 18], "happen": [0, 11], "being": [0, 2, 6, 12, 14, 20], "termin": [0, 8, 12], "rebalanc": [0, 1, 3, 4, 5, 6, 12], "revok": [0, 3, 6, 12, 20], "partit": [0, 1, 2, 3, 6, 7, 8, 10, 11, 12, 20], "assign": [0, 3, 6, 12, 20], "new": [0, 3, 6, 8, 20], "ones": [0, 12, 18], "after": [0, 3, 6, 9, 11, 12, 20], "imposs": 0, "wa": [0, 6, 8, 12, 14, 18], "order": [0, 2, 3, 6, 8, 15, 18, 20], "ensur": [0, 9], "behav": 0, "consist": [0, 2], "upon": [0, 2], "build": [0, 4, 5, 12, 20], "one": [0, 2, 3, 8, 14, 15, 19, 20], "allow": [0, 7, 8, 12, 20], "befor": [0, 6, 8, 12, 15, 18], "same": [0, 1, 3, 6, 18], "few": [0, 1, 20], "simpler": 0, "exampl": [0, 1, 12, 20], "task": [0, 12], "multiprocess": [0, 6], "design": [0, 20], "flexibl": [0, 12], "thei": [0, 2, 8, 15], "function": [0, 3, 4, 5, 6, 7, 8, 10, 11, 12, 15, 16, 17, 18, 19], "user": [0, 17, 19, 20], "everi": [0, 2, 3, 7, 10, 20], "pass": [0, 1, 2, 3, 6, 7, 8, 10, 11, 14, 20], "output": [0, 3, 6, 14], "includ": [0, 6, 8], "synchron": [0, 7, 12, 18], "version": [0, 6], "depend": [0, 12], "kind": [0, 11], "concurr": [0, 17], "requir": [0, 1, 2, 3, 8, 20], "type": [0, 3, 6, 10, 12], "inspect": 0, "whether": [0, 8, 20], "discard": [0, 12, 20], "transform": [0, 6, 14, 18], "its": [0, 10, 12, 20], "content": [0, 8], "produc": [0, 2, 3, 4, 5, 12], "topic": [0, 2, 12, 14], "onli": [0, 6, 9, 10, 20], "need": [0, 3, 7, 10, 12, 18, 20], "wire": [0, 12], "them": [0, 8, 10, 18, 19, 20], "logic": [0, 12, 20], "side": 0, "effect": [0, 1, 18], "variat": 0, "abov": [0, 3], "have": [0, 3, 6, 7, 8, 10, 18, 20], "store": [0, 18], "somewher": 0, "throughput": [0, 6, 7, 18], "intens": 0, "gil": 0, "doe": [0, 6, 7, 8, 13, 20], "advantag": 0, "batch": [0, 6, 12, 15], "via": [0, 7], "share": [0, 18], "larg": [0, 18], "transpar": 0, "carri": [0, 10], "out": [0, 8, 11, 18, 20], "collector": 0, "full": [0, 8, 15], "typic": [0, 6, 14], "storag": 0, "round": 0, "trip": 0, "arroyo": [1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "": [1, 6, 7, 12, 18], "own": [1, 12], "strategi": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19], "intern": [1, 3, 6, 20], "addit": [1, 2, 20], "deal": [1, 10], "correctli": 1, "you": [1, 3, 6, 7, 8, 10, 11, 12, 18, 20], "want": [1, 8, 11, 18, 20], "slow": [1, 12, 14, 17], "extern": [1, 3], "signal": [1, 20], "condit": [1, 15], "achiev": 1, "within": [1, 20], "runtask": [1, 3, 7, 11, 16], "while": [1, 3, 20], "class": [1, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "consumerstrategyfactori": [1, 3, 11], "processingstrategyfactori": [1, 3, 7, 11, 12], "kafkapayload": [1, 2, 3, 7, 11], "def": [1, 3, 6, 7, 11, 20], "__init__": [1, 11], "self": [1, 3, 6, 7, 11], "is_paus": [1, 11], "fals": [1, 2, 3, 6, 10, 11, 20], "create_with_partit": [1, 3, 7, 11, 12], "int": [1, 2, 3, 6, 7, 8, 11, 12, 14, 15, 17, 18], "handle_messag": [1, 3, 11], "print": [1, 3], "f": [1, 3, 6], "msg": [1, 3, 6, 18], "payload": [1, 3, 10, 12, 17], "commitoffset": [1, 3, 7, 9, 11], "recommend": [1, 12], "just": [1, 3, 6, 10, 11], "sleep": 1, "ing": 1, "more": [1, 3, 12, 18, 20], "than": [1, 2, 6, 11, 18], "millisecond": [1, 11], "definit": 1, "long": [1, 6, 11, 18], "prevent": [1, 2], "thing": [1, 18, 20], "occur": [1, 14, 17], "0": [1, 3, 20], "01": 1, "second": [1, 3, 6, 8, 11, 15, 18], "spin": [1, 6], "100": [1, 2, 10], "howev": [1, 11, 18, 20], "background": [1, 6], "impact": [1, 20], "dure": [1, 6, 7, 12], "caution": 2, "break": [2, 18], "otherwis": [2, 20], "offer": 2, "code": [2, 3, 11, 12, 20], "particular": 2, "must": [2, 6, 7, 19], "safe": [2, 7], "drop": [2, 6, 10], "replai": 2, "later": [2, 10, 12], "re": [2, 6, 18, 20], "dlq": [2, 6], "ed": 2, "critic": 2, "relev": 2, "downstream": [2, 8, 10, 15], "rout": 2, "invalid": [2, 6, 16], "becaus": [2, 6, 10, 11, 18], "singl": [2, 6, 12, 14, 19], "caus": [2, 6, 10, 18], "crash": [2, 12], "subsequ": [2, 6, 19], "configur": [2, 3, 6, 18], "streamprocessor": [2, 3, 12], "ani": [2, 12, 18, 20], "invalidmessag": [2, 6, 16], "dlqlimit": 2, "max_invalid_ratio": 2, "float": [2, 6, 8, 12, 15, 18], "none": [2, 3, 6, 7, 8, 10, 12, 15, 18], "max_consecutive_count": 2, "defin": [2, 6, 12], "limit": [2, 18], "place": 2, "number": [2, 6, 8, 12, 20], "exist": 2, "go": 2, "someth": [2, 7, 12, 20], "misconfigur": 2, "bad": [2, 20], "deploi": [2, 20], "prefer": 2, "stop": 2, "altogeth": 2, "fix": [2, 6, 18, 20], "rather": [2, 10, 18], "rerout": 2, "ratio": 2, "count": [2, 6], "per": [2, 6, 8, 11, 18, 20], "basi": 2, "dlqpolici": 2, "dlqproduc": 2, "tstrategypayload": [2, 8, 10, 11, 12, 14, 16, 17, 18], "max_buffered_messages_per_partit": 2, "polici": 2, "stream": [2, 4, 5, 7, 10, 12, 14, 17], "processor": [2, 3, 7, 12, 14, 17], "creation": 2, "offset": [2, 3, 4, 5, 6, 8, 10, 12, 18, 20], "needs_commit": 2, "bool": [2, 6, 10], "true": [2, 3, 7, 20], "valid": 2, "retri": [2, 12], "join": [2, 6, 8, 12], "multipl": [2, 11, 20], "kafkadlqproduc": 2, "field": 2, "ad": [2, 8], "header": [2, 3], "original_partit": 2, "origin": [2, 14, 17], "original_offset": 2, "noopdlqproduc": 2, "arg": [2, 6, 11, 12], "kwd": [2, 12], "tutori": 3, "show": [3, 6, 18], "how": [3, 6, 7, 8, 12, 15], "scratch": 3, "section": [3, 18, 20], "explain": [3, 20], "alreadi": [3, 20], "skip": [3, 7], "command": [3, 11], "docker": 3, "contain": [3, 10, 11, 12, 17], "network": [3, 6, 20], "rm": [3, 11], "v": [3, 18], "zookeeper_volum": 3, "var": 3, "lib": 3, "env": 3, "zookeeper_client_port": 3, "2181": 3, "name": [3, 6, 11, 12], "sentry_zookeep": 3, "p": 3, "confluentinc": 3, "cp": 3, "6": 3, "2": 3, "kafka_volum": 3, "kafka_zookeeper_connect": 3, "kafka_listen": 3, "9093": 3, "9092": [3, 20], "kafka_advertised_listen": 3, "127": 3, "1": [3, 6, 7, 12], "kafka_listener_security_protocol_map": 3, "plaintext": 3, "kafka_inter_broker_listener_nam": 3, "kafka_offsets_topic_replication_factor": 3, "confluent_support_metrics_en": 3, "kafka_log4j_logg": 3, "cluster": 3, "warn": 3, "control": [3, 20], "coordin": 3, "log": [3, 20], "server": [3, 20], "state": [3, 20], "logger": 3, "kafka_log4j_root_loglevel": 3, "kafka_tools_log4j_loglevel": 3, "sentry_kafka": 3, "now": [3, 6, 18, 20], "see": [3, 18, 20], "tool": 3, "onto": 3, "http": [3, 20], "platform": 3, "current": [3, 8, 20], "app": 3, "usag": [3, 6], "html": 3, "kcat": 3, "formerli": 3, "util": [3, 6], "venv": 3, "virtual": 3, "Then": [3, 18], "pip": 3, "sentri": 3, "our": [3, 20], "anoth": [3, 14], "exec": [3, 11], "sourc": 3, "bootstrap": [3, 20], "dest": 3, "level": [3, 11, 20], "thin": 3, "featur": [3, 18], "interfac": [3, 6], "hide": 3, "detail": 3, "lifecycl": 3, "initi": [3, 6], "backend": [3, 6, 11], "import": [3, 6, 14, 20], "build_kafka_consumer_configur": 3, "kafkaconsum": [3, 11], "default_config": 3, "bootstrap_serv": 3, "auto_offset_reset": 3, "latest": [3, 20], "group_id": 3, "group": [3, 12], "subscrib": [3, 20], "timeout": [3, 8, 12, 20], "script": 3, "echo": 3, "b": 3, "t": [3, 10, 12, 18], "appear": 3, "consol": 3, "kei": 3, "valu": [3, 6, 12, 15, 18, 20], "custom": [3, 8, 12, 15, 16, 19, 20], "over": [3, 12], "factori": 3, "close": [3, 8, 12, 18], "runtim": 3, "once_per_second": [3, 7], "processor_factori": 3, "commit_polici": [3, 10], "period": [3, 6, 10, 20], "consumerstrategi": 3, "invers": 3, "chain": [3, 7, 9], "kafkaproduc": 3, "build_kafka_configur": 3, "simpli": 3, "find": [3, 6], "reli": [4, 5], "confluent_kafka": [4, 5, 20], "mainli": [4, 5], "inspir": [4, 5], "simplifi": [4, 5], "what": [4, 10, 18], "get": [4, 18, 20], "start": [4, 11, 18], "architectur": [4, 20], "backpressur": [4, 6, 18, 20], "metric": [4, 9, 18], "attempt": [6, 8, 14, 19, 20], "auto": [6, 7, 18, 20], "instrument": 6, "peopl": 6, "understand": [6, 20], "behavior": [6, 10, 20], "sampl": 6, "buffer": [6, 20], "appropri": 6, "flush": [6, 8, 15, 20], "often": [6, 7, 20], "conform": 6, "protocol": 6, "creat": [6, 8, 18, 19], "your": [6, 7, 10, 11, 12, 20], "metricnam": 6, "mymetr": 6, "increment": [6, 18], "union": [6, 10], "tag": 6, "option": [6, 7, 11], "counter": 6, "given": [6, 18], "record_incr": 6, "gaug": 6, "record_gaug": 6, "emit": [6, 10, 18], "record_tim": 6, "metrics_backend": 6, "configure_metr": 6, "liter": 6, "run_task_with_multiprocess": [6, 18], "size": [6, 15, 18], "byte": [6, 18], "output_batch": 6, "encount": [6, 18], "invalid_messag": 6, "took": 6, "fill": [6, 18], "up": [6, 7, 12, 18, 20], "batch_tim": 6, "reason": [6, 7, 10], "why": 6, "cannot": [6, 10, 20], "satur": 6, "input": 6, "enough": [6, 18], "alloc": [6, 18], "smaller": [6, 18], "increas": [6, 18], "input_block_s": [6, 18], "overflow": [6, 18], "pull": 6, "equal": 6, "devast": 6, "output_block_s": [6, 18], "ha": [6, 7, 8, 11, 12, 14, 20], "combat": 6, "disabl": 6, "explicitli": [6, 18], "runtaskwithmultiprocess": [6, 18], "resiz": [6, 18], "mani": [6, 8, 10, 11, 12, 14, 15, 17, 18, 20], "batches_in_progress": [6, 18], "subprocess": [6, 18], "unexpectedli": 6, "di": [6, 20], "sigchld": 6, "detect": 6, "pool": [6, 18], "unitless": 6, "spent": [6, 8, 15, 18], "due": [6, 12, 20], "handl": [6, 10, 20], "send": [6, 8, 18, 20], "exit": [6, 12], "shutdown": [6, 7, 12], "overlap": 6, "other": [6, 10, 12], "might": 6, "spike": 6, "regular": [6, 10], "durat": 6, "datapoint": 6, "measur": 6, "distinct": 6, "accumul": [6, 8, 15], "keep": [6, 8, 11, 12, 18, 19], "monitor": 6, "low": 6, "callback_nam": 6, "possibl": [6, 20], "on_partitions_assign": 6, "on_partitions_revok": 6, "close_strategi": 6, "create_strategi": 6, "been": [6, 7, 8, 12, 14, 20], "partitions_revok": 6, "partitions_assign": 6, "latenc": [6, 9], "record": [6, 9], "underli": 6, "rdkafka": 6, "prefetch": 6, "resum": 6, "fetch": [6, 18], "total_queue_s": 6, "healthcheck": 6, "file": [6, 11, 12, 20], "touch": [6, 11], "filterstep": [6, 10], "dropped_messag": 6, "For": [6, 10, 14, 17, 18], "conveni": 6, "machin": 6, "readabl": 6, "load": [6, 7, 18], "importlib": 6, "resourc": [6, 12], "json": 6, "joinpath": 6, "metricdef": 6, "open": 6, "metric_def": 6, "kwarg": [6, 12], "str": [6, 11, 12], "forc": [6, 7], "unless": 6, "manual": [7, 20], "rest": 7, "alwai": [7, 9, 14], "e": [7, 10, 19], "mean": [7, 18, 20], "next_offset": [7, 12], "never": [7, 18], "expos": 7, "position_to_commit": 7, "easier": 7, "high": [7, 8], "add": [7, 20], "lot": [7, 18, 20], "system": [7, 10], "throttl": 7, "commitpolici": [7, 10], "specifi": [7, 17], "frequenc": 7, "minimum": 7, "between": [7, 18], "sure": [7, 20], "easiest": 7, "last": [7, 9], "myconsumerfactoryfactori": 7, "my_processing_funct": 7, "do_someth": 7, "unfold": 8, "batchstep": 8, "max_batch_s": [8, 15, 18], "max_batch_tim": [8, 15, 18], "next_step": [8, 10, 11, 14, 15, 16, 17, 18, 19], "mutablesequ": 8, "basevalu": [8, 12, 15, 19], "compute_batch_s": [8, 15], "callabl": [8, 10, 15, 16, 17, 18, 19], "repres": [8, 12], "valuesbatch": 8, "object": [8, 17], "sequenc": [8, 19], "both": 8, "watermark": 8, "maximum": [8, 15], "receiv": [8, 12, 14, 15, 17, 19], "sinc": [8, 12, 17, 18], "sent": 8, "though": 8, "highest": [8, 18], "observ": [8, 18], "still": [8, 18], "committ": [8, 12], "lower": 8, "propag": [8, 15], "thrown": [8, 12, 15], "paramet": [8, 12, 15, 18], "much": [8, 15, 20], "tri": 8, "matter": 8, "filteredpayload": [8, 10, 12, 14, 16, 17, 18, 19], "without": [8, 18, 20], "previou": [8, 12, 18, 20], "try": 8, "again": [8, 18, 20], "introduc": 8, "duplic": 8, "unbatchstep": 8, "explod": [8, 19], "remain": [8, 19], "prior": [9, 12], "determin": 10, "sometim": 10, "actual": [10, 20], "desir": 10, "interv": [10, 11, 12], "That": [10, 18], "sentinel": 10, "those": [10, 18], "abl": [10, 18], "subtyp": 10, "doesn": [10, 18], "compos": 10, "default": [10, 11, 18, 20], "despit": 10, "tell": [10, 11, 12], "turn": 11, "unhealthi": 11, "max": [11, 15], "m": [11, 20], "kick": [11, 20], "300000": 11, "5": 11, "minut": 11, "noth": [11, 13], "pod": [11, 20], "well": 11, "repeatedli": 11, "indic": [11, 16], "health": 11, "tmp": 11, "txt": 11, "kubernet": 11, "live": 11, "look": [11, 18, 20], "apivers": 11, "v1": 11, "metadata": 11, "label": 11, "spec": 11, "imag": 11, "registri": 11, "k8": 11, "busybox": 11, "bin": 11, "my_arroyo_consum": 11, "livenessprob": 11, "initialdelaysecond": 11, "periodsecond": 11, "320": 11, "healthcheck_fil": 11, "filepath": 11, "everytim": 11, "overal": 11, "debounc": 11, "compon": 12, "normal": [12, 18], "don": [12, 18, 20], "encourag": 12, "plug": 12, "nevertheless": 12, "against": 12, "unabl": 12, "rate": [12, 18], "incom": 12, "abc": 12, "cours": 12, "intention": 12, "prescript": 12, "afford": 12, "signific": 12, "degre": 12, "variou": 12, "instanc": [12, 18], "No": [12, 20], "finish": 12, "were": [12, 18], "releas": 12, "longer": 12, "socket": 12, "until": [12, 15, 20], "previous": 12, "reach": [12, 20], "grace": 12, "revoc": [12, 20], "progress": 12, "continu": [12, 18], "statu": 12, "schedul": [12, 20], "iter": [12, 19], "amount": 12, "exce": 12, "caller": 12, "assum": 12, "successfulli": [12, 14], "impli": 12, "capac": [12, 18], "abandon": 12, "wrap": 12, "seri": 12, "recreat": 12, "recent": 12, "entir": [12, 20], "note": [12, 18], "also": [12, 18, 20], "properti": 12, "tmessagepayload": 12, "replac": 12, "treplac": 12, "timestamp": 12, "datetim": 12, "brokervalu": 12, "present": 12, "either": 12, "els": 12, "payload_unfilt": 12, "index": 12, "span": 12, "max_buffer_s": 14, "10000": 14, "destin": [14, 20], "could": [14, 20], "pend": [14, 17], "notifi": [14, 17], "error": [14, 17, 20], "constructor": 14, "referenc": 14, "tresult": [15, 16, 17, 18], "tpayload": 15, "initial_valu": 15, "hit": [15, 18], "run_task": 16, "put": 16, "run_task_in_thread": 17, "runtaskinthread": 17, "processing_funct": 17, "max_pending_futur": 17, "bound": 17, "avoid": [17, 18], "modifi": 17, "differ": [17, 20], "protect": 17, "lock": [17, 18], "multiprocessingpool": 18, "max_input_block_s": 18, "max_output_block_s": 18, "across": 18, "stdlib": 18, "rebal": [18, 20], "message_s": 18, "expect": 18, "averag": 18, "implicitli": 18, "broken": 18, "automat": 18, "adjust": 18, "adapt": 18, "traffic": 18, "mind": [18, 20], "experiment": 18, "less": 18, "production": 18, "data": 18, "enabl": [18, 20], "upper": 18, "point": 18, "line": [18, 20], "bottleneck": 18, "good": [18, 20], "reserv": 18, "But": 18, "num_process": 18, "faster": [18, 20], "tweak": 18, "optim": 18, "cost": 18, "amort": 18, "spend": 18, "flat": 18, "perfectli": 18, "straight": 18, "constant": 18, "neither": 18, "ey": 18, "ran": 18, "respons": 18, "realli": 18, "expens": [18, 20], "affect": 18, "confus": 18, "best": 18, "anywai": 18, "chunk": 18, "veri": [18, 20], "won": [18, 20], "notic": 18, "regress": 18, "sens": 18, "rid": 18, "issu": [18, 20], "altern": 18, "let": [18, 20], "backlog": 18, "total": 18, "lag": 18, "ram": 18, "2x": 18, "fine": 18, "afterward": 18, "remov": [18, 20], "potenti": 18, "tinput": 19, "toutput": 19, "collect": 19, "accord": 19, "__iter__": 19, "testabl": 20, "document": 20, "outlin": 20, "intricaci": 20, "easi": 20, "awai": 20, "worri": 20, "about": 20, "reproduc": 20, "environ": 20, "visual": 20, "event": 20, "driven": 20, "view": 20, "push": 20, "diagram": 20, "below": 20, "accur": 20, "model": 20, "persist": 20, "read": 20, "conf": 20, "localhost": 20, "id": 20, "my_group": 20, "reset": 20, "my_top": 20, "send_to_destin": 20, "process_messag": 20, "satisfi": 20, "mention": 20, "page": 20, "subsect": 20, "By": 20, "To": 20, "lead": 20, "throw": 20, "lost": 20, "know": 20, "config": 20, "u": 20, "sever": 20, "hurt": 20, "replic": 20, "inform": 20, "And": 20, "pure": 20, "descript": 20, "append": 20, "len": 20, "batch_siz": 20, "hand": 20, "wave": 20, "sai": 20, "destination_top": 20, "At": 20, "ever": 20, "transmiss": 20, "delivery_callback": 20, "on_deliveri": 20, "n": 20, "exactli": 20, "off": 20, "whenev": 20, "idea": 20, "annoi": 20, "imagin": 20, "decis": 20, "made": 20, "pick": 20, "on_revok": 20, "flush_current_batch": 20, "core": 20, "mechan": 20, "flow": 20, "clear": 20, "possibli": 20, "invok": 20, "respect": 20, "gotcha": 20, "kept": 20, "comit": 20, "api": 20, "www": 20, "educba": 20, "com": 20}, "objects": {"arroyo": [[2, 0, 0, "-", "dlq"], [12, 0, 0, "-", "types"]], "arroyo.dlq": [[2, 1, 1, "", "DlqLimit"], [2, 1, 1, "", "DlqPolicy"], [2, 2, 1, "", "InvalidMessage"], [2, 1, 1, "", "KafkaDlqProducer"], [2, 1, 1, "", "NoopDlqProducer"]], "arroyo.processing.strategies": [[12, 0, 0, "-", "abstract"], [8, 0, 0, "-", "batching"], [9, 0, 0, "-", "commit"], [10, 0, 0, "-", "filter"], [11, 0, 0, "-", "healthcheck"], [13, 0, 0, "-", "noop"], [14, 0, 0, "-", "produce"], [15, 0, 0, "-", "reduce"], [16, 0, 0, "-", "run_task"], [17, 0, 0, "-", "run_task_in_threads"], [18, 0, 0, "-", "run_task_with_multiprocessing"], [19, 0, 0, "-", "unfold"]], "arroyo.processing.strategies.abstract": [[12, 2, 1, "", "MessageRejected"], [12, 1, 1, "", "ProcessingStrategy"], [12, 1, 1, "", "ProcessingStrategyFactory"]], "arroyo.processing.strategies.abstract.ProcessingStrategy": [[12, 3, 1, "", "close"], [12, 3, 1, "", "join"], [12, 3, 1, "", "poll"], [12, 3, 1, "", "submit"], [12, 3, 1, "", "terminate"]], "arroyo.processing.strategies.abstract.ProcessingStrategyFactory": [[12, 3, 1, "", "create_with_partitions"], [12, 3, 1, "", "shutdown"]], "arroyo.processing.strategies.batching": [[8, 1, 1, "", "BatchStep"], [8, 1, 1, "", "UnbatchStep"]], "arroyo.processing.strategies.batching.BatchStep": [[8, 3, 1, "", "join"], [8, 3, 1, "", "submit"]], "arroyo.processing.strategies.commit": [[9, 1, 1, "", "CommitOffsets"]], "arroyo.processing.strategies.filter": [[10, 1, 1, "", "FilterStep"]], "arroyo.processing.strategies.healthcheck": [[11, 1, 1, "", "Healthcheck"]], "arroyo.processing.strategies.noop": [[13, 1, 1, "", "Noop"]], "arroyo.processing.strategies.produce": [[14, 1, 1, "", "Produce"]], "arroyo.processing.strategies.reduce": [[15, 1, 1, "", "Reduce"]], "arroyo.processing.strategies.run_task": [[16, 1, 1, "", "RunTask"]], "arroyo.processing.strategies.run_task_in_threads": [[17, 1, 1, "", "RunTaskInThreads"]], "arroyo.processing.strategies.run_task_with_multiprocessing": [[18, 1, 1, "", "RunTaskWithMultiprocessing"]], "arroyo.processing.strategies.unfold": [[19, 1, 1, "", "Unfold"]], "arroyo.types": [[12, 1, 1, "", "BaseValue"], [12, 1, 1, "", "BrokerValue"], [12, 1, 1, "", "Commit"], [12, 1, 1, "", "FilteredPayload"], [12, 1, 1, "", "Message"], [12, 1, 1, "", "Partition"], [12, 1, 1, "", "Topic"], [12, 1, 1, "", "Value"]], "arroyo.types.BaseValue": [[12, 4, 1, "", "committable"], [12, 4, 1, "", "payload"], [12, 3, 1, "", "replace"], [12, 4, 1, "", "timestamp"]], "arroyo.types.BrokerValue": [[12, 4, 1, "", "committable"], [12, 4, 1, "", "next_offset"], [12, 5, 1, "", "offset"], [12, 5, 1, "", "partition"], [12, 4, 1, "", "payload"], [12, 3, 1, "", "replace"], [12, 5, 1, "", "timestamp"]], "arroyo.types.Message": [[12, 4, 1, "", "committable"], [12, 4, 1, "", "payload"], [12, 4, 1, "", "payload_unfiltered"], [12, 3, 1, "", "replace"], [12, 4, 1, "", "timestamp"], [12, 5, 1, "", "value"]], "arroyo.types.Partition": [[12, 5, 1, "", "index"], [12, 5, 1, "", "topic"]], "arroyo.types.Topic": [[12, 5, 1, "", "name"]], "arroyo.types.Value": [[12, 4, 1, "", "committable"], [12, 4, 1, "", "payload"], [12, 3, 1, "", "replace"], [12, 4, 1, "", "timestamp"]], "arroyo.utils": [[6, 0, 0, "-", "metrics"]], "arroyo.utils.metrics": [[6, 1, 1, "", "Metrics"], [6, 6, 1, "", "configure_metrics"]], "arroyo.utils.metrics.Metrics": [[6, 3, 1, "", "gauge"], [6, 3, 1, "", "increment"], [6, 3, 1, "", "timing"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:exception", "3": "py:method", "4": "py:property", "5": "py:attribute", "6": "py:function"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "exception", "Python exception"], "3": ["py", "method", "Python method"], "4": ["py", "property", "Python property"], "5": ["py", "attribute", "Python attribute"], "6": ["py", "function", "Python function"]}, "titleterms": {"arroyo": [0, 3, 20], "architectur": 0, "stream": [0, 3], "interfac": [0, 12], "engin": 0, "high": [0, 20], "level": 0, "strategi": [0, 12], "backpressur": 1, "dead": 2, "letter": 2, "queue": 2, "get": 3, "start": 3, "setup": 3, "kafka": [3, 20], "zookeep": 3, "instal": 3, "kafkacat": 3, "develop": 3, "environ": 3, "creat": 3, "two": 3, "topic": [3, 20], "basic": 3, "consum": [3, 18, 20], "add": 3, "some": 3, "us": 3, "logic": 3, "further": 3, "exampl": 3, "content": 4, "metric": 6, "avail": 6, "api": 6, "commit": [7, 9, 20], "offset": [7, 9], "batch": [8, 18, 20], "unbatch": 8, "filter": 10, "healthcheck": 11, "process": [12, 18], "messag": 12, "noop": 13, "produc": [14, 20], "reduc": 15, "fold": 15, "run": [16, 17, 18], "task": [16, 17, 18], "thread": 17, "multiprocess": 18, "number": 18, "input": 18, "output": 18, "buffer": 18, "how": [18, 20], "tune": 18, "your": 18, "unfold": 19, "what": 20, "i": 20, "goal": 20, "why": 20, "simpl": 20, "doesn": 20, "t": 20, "cut": 20, "It": 20, "provid": 20, "deliveri": 20, "guarante": 20, "throughput": 20, "reliabl": 20, "deal": 20, "With": 20, "rebalanc": 20, "when": 20, "can": 20, "happen": 20, "affect": 20, "librdkafka": 20, "": 20, "callback": 20, "hell": 20, "conclus": 20, "appendix": 20, "group": 20}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx": 58}, "alltitles": {"Arroyo Architecture": [[0, "arroyo-architecture"]], "Streaming Interface and Streaming Engine": [[0, "streaming-interface-and-streaming-engine"]], "High level strategies": [[0, "high-level-strategies"]], "Backpressure": [[1, "backpressure"]], "Dead letter queues": [[2, "dead-letter-queues"]], "Getting started with Arroyo": [[3, "getting-started-with-arroyo"]], "Setup": [[3, "setup"]], "Kafka and Zookeeper": [[3, "kafka-and-zookeeper"]], "Install Kafkacat": [[3, "install-kafkacat"]], "Development environment": [[3, "development-environment"]], "Create two Kafka topics": [[3, "create-two-kafka-topics"]], "Create a basic consumer": [[3, "create-a-basic-consumer"]], "Creating a basic consumer": [[3, "creating-a-basic-consumer"]], "Create a streaming consumer": [[3, "create-a-streaming-consumer"]], "Add some useful logic": [[3, "add-some-useful-logic"]], "Further examples": [[3, "further-examples"]], "Contents:": [[4, "contents"]], "Metrics": [[6, "metrics"]], "Available Metrics": [[6, "available-metrics"]], "API": [[6, "module-arroyo.utils.metrics"]], "Committing offsets": [[7, "committing-offsets"]], "Batch and Unbatch": [[8, "batch-and-unbatch"]], "Commit offsets": [[9, "commit-offsets"]], "Filter": [[10, "module-arroyo.processing.strategies.filter"]], "Healthchecks": [[11, "healthchecks"]], "Processing Strategies": [[12, "processing-strategies"]], "Strategy interface": [[12, "strategy-interface"]], "Messages": [[12, "module-arroyo.types"]], "Noop": [[13, "module-arroyo.processing.strategies.noop"]], "Produce": [[14, "module-arroyo.processing.strategies.produce"]], "Reduce (Fold)": [[15, "reduce-fold"]], "Run Task": [[16, "module-arroyo.processing.strategies.run_task"]], "Run Task in Threads": [[17, "module-arroyo.processing.strategies.run_task_in_threads"]], "Run Task with Multiprocessing": [[18, "module-arroyo.processing.strategies.run_task_with_multiprocessing"]], "Number of processes": [[18, "number-of-processes"]], "Batching": [[18, "batching"]], "Input and output buffers": [[18, "input-and-output-buffers"]], "How to tune your consumer": [[18, "how-to-tune-your-consumer"]], "Unfold": [[19, "unfold"]], "What is Arroyo for?": [[20, "what-is-arroyo-for"]], "Goals": [[20, "goals"]], "Why Simple Doesn\u2019t Cut It": [[20, "why-simple-doesnt-cut-it"]], "Providing delivery guarantees": [[20, "providing-delivery-guarantees"]], "High Throughput": [[20, "high-throughput"]], "Reliable High Throughput Batched Producers": [[20, "reliable-high-throughput-batched-producers"]], "Dealing With Rebalancing": [[20, "dealing-with-rebalancing"]], "What is Rebalancing": [[20, "what-is-rebalancing"]], "When Rebalancing Can Happen": [[20, "when-rebalancing-can-happen"]], "How Rebalancing Affects a Consumer": [[20, "how-rebalancing-affects-a-consumer"]], "librdkafka\u2019s Callback Hell": [[20, "librdkafkas-callback-hell"]], "Conclusion": [[20, "conclusion"]], "Appendix": [[20, "appendix"]], "Committing to a Kafka Topic": [[20, "committing-to-a-kafka-topic"]], "What is a Kafka Consumer Group": [[20, "what-is-a-kafka-consumer-group"]]}, "indexentries": {"dlqlimit (class in arroyo.dlq)": [[2, "arroyo.dlq.DlqLimit"]], "dlqpolicy (class in arroyo.dlq)": [[2, "arroyo.dlq.DlqPolicy"]], "invalidmessage": [[2, "arroyo.dlq.InvalidMessage"]], "kafkadlqproducer (class in arroyo.dlq)": [[2, "arroyo.dlq.KafkaDlqProducer"]], "noopdlqproducer (class in arroyo.dlq)": [[2, "arroyo.dlq.NoopDlqProducer"]], "arroyo.dlq": [[2, "module-arroyo.dlq"]], "module": [[2, "module-arroyo.dlq"], [6, "module-arroyo.utils.metrics"], [8, "module-arroyo.processing.strategies.batching"], [9, "module-arroyo.processing.strategies.commit"], [10, "module-arroyo.processing.strategies.filter"], [11, "module-arroyo.processing.strategies.healthcheck"], [12, "module-arroyo.processing.strategies.abstract"], [12, "module-arroyo.types"], [13, "module-arroyo.processing.strategies.noop"], [14, "module-arroyo.processing.strategies.produce"], [15, "module-arroyo.processing.strategies.reduce"], [16, "module-arroyo.processing.strategies.run_task"], [17, "module-arroyo.processing.strategies.run_task_in_threads"], [18, "module-arroyo.processing.strategies.run_task_with_multiprocessing"], [19, "module-arroyo.processing.strategies.unfold"]], "metrics (class in arroyo.utils.metrics)": [[6, "arroyo.utils.metrics.Metrics"]], "arroyo.utils.metrics": [[6, "module-arroyo.utils.metrics"]], "configure_metrics() (in module arroyo.utils.metrics)": [[6, "arroyo.utils.metrics.configure_metrics"]], "gauge() (arroyo.utils.metrics.metrics method)": [[6, "arroyo.utils.metrics.Metrics.gauge"]], "increment() (arroyo.utils.metrics.metrics method)": [[6, "arroyo.utils.metrics.Metrics.increment"]], "timing() (arroyo.utils.metrics.metrics method)": [[6, "arroyo.utils.metrics.Metrics.timing"]], "batchstep (class in arroyo.processing.strategies.batching)": [[8, "arroyo.processing.strategies.batching.BatchStep"]], "unbatchstep (class in arroyo.processing.strategies.batching)": [[8, "arroyo.processing.strategies.batching.UnbatchStep"]], "arroyo.processing.strategies.batching": [[8, "module-arroyo.processing.strategies.batching"]], "join() (arroyo.processing.strategies.batching.batchstep method)": [[8, "arroyo.processing.strategies.batching.BatchStep.join"]], "submit() (arroyo.processing.strategies.batching.batchstep method)": [[8, "arroyo.processing.strategies.batching.BatchStep.submit"]], "commitoffsets (class in arroyo.processing.strategies.commit)": [[9, "arroyo.processing.strategies.commit.CommitOffsets"]], "arroyo.processing.strategies.commit": [[9, "module-arroyo.processing.strategies.commit"]], "filterstep (class in arroyo.processing.strategies.filter)": [[10, "arroyo.processing.strategies.filter.FilterStep"]], "arroyo.processing.strategies.filter": [[10, "module-arroyo.processing.strategies.filter"]], "healthcheck (class in arroyo.processing.strategies.healthcheck)": [[11, "arroyo.processing.strategies.healthcheck.Healthcheck"]], "arroyo.processing.strategies.healthcheck": [[11, "module-arroyo.processing.strategies.healthcheck"]], "basevalue (class in arroyo.types)": [[12, "arroyo.types.BaseValue"]], "brokervalue (class in arroyo.types)": [[12, "arroyo.types.BrokerValue"]], "commit (class in arroyo.types)": [[12, "arroyo.types.Commit"]], "filteredpayload (class in arroyo.types)": [[12, "arroyo.types.FilteredPayload"]], "message (class in arroyo.types)": [[12, "arroyo.types.Message"]], "messagerejected": [[12, "arroyo.processing.strategies.abstract.MessageRejected"]], "partition (class in arroyo.types)": [[12, "arroyo.types.Partition"]], "processingstrategy (class in arroyo.processing.strategies.abstract)": [[12, "arroyo.processing.strategies.abstract.ProcessingStrategy"]], "processingstrategyfactory (class in arroyo.processing.strategies.abstract)": [[12, "arroyo.processing.strategies.abstract.ProcessingStrategyFactory"]], "topic (class in arroyo.types)": [[12, "arroyo.types.Topic"]], "value (class in arroyo.types)": [[12, "arroyo.types.Value"]], "arroyo.processing.strategies.abstract": [[12, "module-arroyo.processing.strategies.abstract"]], "arroyo.types": [[12, "module-arroyo.types"]], "close() (arroyo.processing.strategies.abstract.processingstrategy method)": [[12, "arroyo.processing.strategies.abstract.ProcessingStrategy.close"]], "committable (arroyo.types.basevalue property)": [[12, "arroyo.types.BaseValue.committable"]], "committable (arroyo.types.brokervalue property)": [[12, "arroyo.types.BrokerValue.committable"]], "committable (arroyo.types.message property)": [[12, "arroyo.types.Message.committable"]], "committable (arroyo.types.value property)": [[12, "arroyo.types.Value.committable"]], "create_with_partitions() (arroyo.processing.strategies.abstract.processingstrategyfactory method)": [[12, "arroyo.processing.strategies.abstract.ProcessingStrategyFactory.create_with_partitions"]], "index (arroyo.types.partition attribute)": [[12, "arroyo.types.Partition.index"]], "join() (arroyo.processing.strategies.abstract.processingstrategy method)": [[12, "arroyo.processing.strategies.abstract.ProcessingStrategy.join"]], "name (arroyo.types.topic attribute)": [[12, "arroyo.types.Topic.name"]], "next_offset (arroyo.types.brokervalue property)": [[12, "arroyo.types.BrokerValue.next_offset"]], "offset (arroyo.types.brokervalue attribute)": [[12, "arroyo.types.BrokerValue.offset"]], "partition (arroyo.types.brokervalue attribute)": [[12, "arroyo.types.BrokerValue.partition"]], "payload (arroyo.types.basevalue property)": [[12, "arroyo.types.BaseValue.payload"]], "payload (arroyo.types.brokervalue property)": [[12, "arroyo.types.BrokerValue.payload"]], "payload (arroyo.types.message property)": [[12, "arroyo.types.Message.payload"]], "payload (arroyo.types.value property)": [[12, "arroyo.types.Value.payload"]], "payload_unfiltered (arroyo.types.message property)": [[12, "arroyo.types.Message.payload_unfiltered"]], "poll() (arroyo.processing.strategies.abstract.processingstrategy method)": [[12, "arroyo.processing.strategies.abstract.ProcessingStrategy.poll"]], "replace() (arroyo.types.basevalue method)": [[12, "arroyo.types.BaseValue.replace"]], "replace() (arroyo.types.brokervalue method)": [[12, "arroyo.types.BrokerValue.replace"]], "replace() (arroyo.types.message method)": [[12, "arroyo.types.Message.replace"]], "replace() (arroyo.types.value method)": [[12, "arroyo.types.Value.replace"]], "shutdown() (arroyo.processing.strategies.abstract.processingstrategyfactory method)": [[12, "arroyo.processing.strategies.abstract.ProcessingStrategyFactory.shutdown"]], "submit() (arroyo.processing.strategies.abstract.processingstrategy method)": [[12, "arroyo.processing.strategies.abstract.ProcessingStrategy.submit"]], "terminate() (arroyo.processing.strategies.abstract.processingstrategy method)": [[12, "arroyo.processing.strategies.abstract.ProcessingStrategy.terminate"]], "timestamp (arroyo.types.basevalue property)": [[12, "arroyo.types.BaseValue.timestamp"]], "timestamp (arroyo.types.brokervalue attribute)": [[12, "arroyo.types.BrokerValue.timestamp"]], "timestamp (arroyo.types.message property)": [[12, "arroyo.types.Message.timestamp"]], "timestamp (arroyo.types.value property)": [[12, "arroyo.types.Value.timestamp"]], "topic (arroyo.types.partition attribute)": [[12, "arroyo.types.Partition.topic"]], "value (arroyo.types.message attribute)": [[12, "arroyo.types.Message.value"]], "noop (class in arroyo.processing.strategies.noop)": [[13, "arroyo.processing.strategies.noop.Noop"]], "arroyo.processing.strategies.noop": [[13, "module-arroyo.processing.strategies.noop"]], "produce (class in arroyo.processing.strategies.produce)": [[14, "arroyo.processing.strategies.produce.Produce"]], "arroyo.processing.strategies.produce": [[14, "module-arroyo.processing.strategies.produce"]], "reduce (class in arroyo.processing.strategies.reduce)": [[15, "arroyo.processing.strategies.reduce.Reduce"]], "arroyo.processing.strategies.reduce": [[15, "module-arroyo.processing.strategies.reduce"]], "runtask (class in arroyo.processing.strategies.run_task)": [[16, "arroyo.processing.strategies.run_task.RunTask"]], "arroyo.processing.strategies.run_task": [[16, "module-arroyo.processing.strategies.run_task"]], "runtaskinthreads (class in arroyo.processing.strategies.run_task_in_threads)": [[17, "arroyo.processing.strategies.run_task_in_threads.RunTaskInThreads"]], "arroyo.processing.strategies.run_task_in_threads": [[17, "module-arroyo.processing.strategies.run_task_in_threads"]], "runtaskwithmultiprocessing (class in arroyo.processing.strategies.run_task_with_multiprocessing)": [[18, "arroyo.processing.strategies.run_task_with_multiprocessing.RunTaskWithMultiprocessing"]], "arroyo.processing.strategies.run_task_with_multiprocessing": [[18, "module-arroyo.processing.strategies.run_task_with_multiprocessing"]], "unfold (class in arroyo.processing.strategies.unfold)": [[19, "arroyo.processing.strategies.unfold.Unfold"]], "arroyo.processing.strategies.unfold": [[19, "module-arroyo.processing.strategies.unfold"]]}}) \ No newline at end of file diff --git a/strategies/batching.html b/strategies/batching.html new file mode 100644 index 00000000..a0a01ae7 --- /dev/null +++ b/strategies/batching.html @@ -0,0 +1,245 @@ + + + + + Batch and Unbatch - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Batch and Unbatch

+

Accumulate messages into a batch and pass to the next step. +The batch and unbatch strategies are based on reduce and unfold. +Use reduce/unfold instead if you want to provide custom +accumulator/generator functions.

+
+
+class arroyo.processing.strategies.batching.BatchStep(max_batch_size: int, max_batch_time: float, next_step: ProcessingStrategy[MutableSequence[BaseValue[TStrategyPayload]]], compute_batch_size: Callable[[BaseValue[TStrategyPayload]], int] | None = None)
+

Accumulates messages into a batch. When the batch is full, this +strategy submits it to the next step.

+

A batch is represented as a ValuesBatch object which is a sequence +of BaseValue. This includes both the messages and the high offset +watermark.

+

A messages batch is closed and submitted when the maximum number of +messages is received or when the max_batch_time has passed since the +first message was received.

+

This step does not require in order processing. If messages are sent +out of order, though, the highest observed offset per partition is +still the committable one, whether or not all messages with lower +offsets have been observed by this step.

+

This strategy propagates MessageRejected exceptions from the +downstream steps if they are thrown.

+
+
Parameters:
+
    +
  • max_batch_size – How many messages should be reduced into one at maximum.

  • +
  • max_batch_time – How much time (in seconds) should be spent reducing +messages together before flushing the batch.

  • +
+
+
+
+
+join(timeout: float | None = None) None
+

Terminates the strategy by joining the following step. +This method tries to flush the current batch no matter +whether the batch is ready or not.

+
+ +
+
+submit(message: Message[FilteredPayload | TStrategyPayload]) None
+

Accumulates messages in the current batch. +A new batch is created at the first message received.

+

This method tries to flush before adding the message +to the current batch. This is so that, if we receive +MessageRejected exception from the following step, +we can propagate the exception without processing the +new message. This allows the previous step to try again +without introducing duplications.

+
+ +
+ +
+
+class arroyo.processing.strategies.batching.UnbatchStep(next_step: ProcessingStrategy[FilteredPayload | TStrategyPayload])
+

This processing step receives batches and explodes them thus sending +the content to the next step message by message.

+

A batch is represented as a ValuesBatch object.

+

If this step receives a MessageRejected exception from the next +step it would keep the remaining messages and attempt to submit +them at the following call to poll

+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/strategies/commit_offsets.html b/strategies/commit_offsets.html new file mode 100644 index 00000000..b5f165fc --- /dev/null +++ b/strategies/commit_offsets.html @@ -0,0 +1,187 @@ + + + + + Commit offsets - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Commit offsets

+

Should be used as the last strategy in the chain, to ensure +that offsets are only committed once all processing is complete.

+
+
+class arroyo.processing.strategies.commit.CommitOffsets(commit: Commit)
+

Commits offset and records consumer latency metric.

+

This should always be used as the last step in a chain of processing +strategies. It commits offsets back to the broker after all prior +processing of that message is completed.

+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/strategies/filter.html b/strategies/filter.html new file mode 100644 index 00000000..2fbd3dc3 --- /dev/null +++ b/strategies/filter.html @@ -0,0 +1,200 @@ + + + + + Filter - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Filter

+
+
+class arroyo.processing.strategies.filter.FilterStep(function: Callable[[Message[TStrategyPayload]], bool], next_step: ProcessingStrategy[FilteredPayload | TStrategyPayload], commit_policy: CommitPolicy | None = None)
+

Determines if a message should be submitted to the next processing step.

+

FilterStep takes a callback, function, and if that callback returns +False, the message is dropped.

+

Sometimes that behavior is not actually desirable because streams of +messages is what makes the consumer commit in regular intervals. If you +filter 100% of messages for a period of time, your consumer may not commit +its offsets as a result.

+

For that scenario, you can pass your CommitPolicy to FilterStep. That +will cause FilterStep to emit “sentinel messages” that contain no +payload, but only carry forward partition offsets for later strategies to +commit. Those messages have a payload of type FilteredPayload.

+

For that reason, basically every strategy needs to be able to handle +Message[Union[FilteredPayload, T]] instead of Message[T], i.e. it needs +to subtype ProcessingStrategy[Union[FilteredPayload, TStrategyPayload]]. +If it doesn’t, and rather just handles the regular Message[T], it cannot +be composed with this step, and many other default strategies of arroyo.

+

If no CommitPolicy is passed, no “sentinel messages” are emitted and +downstream steps do not have to deal with such messages (despite the type +system telling them so).

+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/strategies/healthcheck.html b/strategies/healthcheck.html new file mode 100644 index 00000000..17a32df7 --- /dev/null +++ b/strategies/healthcheck.html @@ -0,0 +1,243 @@ + + + + + Healthchecks - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Healthchecks

+

If your code blocks for too long in the main thread, the consumer can turn +unhealthy.

+

Kafka has a setting called max.poll.interval.ms for this that tells Kafka +to kick the consumer out of the broker after this many milliseconds of not polling.

+

You can pass this option into arroyo.backends.kafka.consumer.KafkaConsumer like so:

+
consumer = KafkaConsumer(
+    {
+        "max.poll.interval.ms": 300000, # default 5 minutes
+    }
+)
+
+
+

However, this will not shut down the consumer, it will just keep running doing +nothing (because it is blocked in the main thread). You want a pod-level +healthcheck as well.

+

Arroyo supports touching a file repeatedly from the main thread to indicate +health. Start your pipeline with the +arroyo.processing.strategies.healthcheck.Healthcheck strategy.

+
def handle_message(message: Message[KafkaPayload]) -> Message[KafkaPayload]:
+    ...
+    return message
+
+class ConsumerStrategyFactory(ProcessingStrategyFactory[KafkaPayload]):
+    def __init__(self):
+        self.is_paused = False
+
+    def create_with_partitions(
+        self,
+        commit: Commit,
+        partitions: Mapping[Partition, int],
+    ) -> ProcessingStrategy[KafkaPayload]:
+        step = RunTask(handle_message, CommitOffsets(commit))
+        return Healthcheck("/tmp/health.txt", step)
+
+
+

The Kubernetes liveness +command would look like:

+
apiVersion: v1
+kind: Pod
+metadata:
+  labels:
+    test: liveness
+  name: liveness-exec
+spec:
+  containers:
+  - name: liveness
+    image: registry.k8s.io/busybox
+    args:
+      - bin/my_arroyo_consumer
+    livenessProbe:
+      exec:
+        command:
+        - rm
+        - /tmp/health.txt
+      initialDelaySeconds: 5
+      periodSeconds: 320  # should be higher than max.poll.interval.ms
+
+
+
+
+class arroyo.processing.strategies.healthcheck.Healthcheck(healthcheck_file: str, next_step: ProcessingStrategy[TStrategyPayload])
+

A strategy that takes a filepath, and touches that file everytime +Strategy.poll is called. If that function is not called multiple times +per minute, it indicates that the consumer is overall unhealthy.

+

File touches are debounced to happen once per second at most.

+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/strategies/index.html b/strategies/index.html new file mode 100644 index 00000000..850a096c --- /dev/null +++ b/strategies/index.html @@ -0,0 +1,527 @@ + + + + + Processing Strategies - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Processing Strategies

+

The processing strategies are the components to be wired together to +build a consumer.

+
+

Strategy interface

+

We normally don’t recommend writing your own strategy, and encourage you to use +built-in ones such as “reduce” or “run task” to plug in your application logic. +Nevertheless, all arroyo strategies are written against the following interface:

+
+
+exception arroyo.processing.strategies.abstract.MessageRejected
+

Bases: Exception

+

MessageRejected should be raised in a processing strategy’s submit method +if it is unable to keep up with the rate of incoming messages. It tells +the consumer to slow down and retry the message later.

+
+ +
+
+class arroyo.processing.strategies.abstract.ProcessingStrategy(*args, **kwds)
+

Bases: ABC, Generic[TStrategyPayload]

+

A processing strategy defines how a stream processor processes messages +during the course of a single assignment. The processor is instantiated +when the assignment is received, and closed when the assignment is +revoked.

+

This interface is intentionally not prescriptive, and affords a +significant degree of flexibility for the various implementations.

+
+
+abstract close() None
+

Close this instance. No more messages should be accepted by the +instance after this method has been called.

+

This method should not block. Once this strategy instance has +finished processing (or discarded) all messages that were submitted +prior to this method being called, the strategy should commit its +partition offsets and release any resources that will no longer be +used (threads, processes, sockets, files, etc.)

+
+ +
+
+abstract join(timeout: float | None = None) None
+

Block until the processing strategy has completed all previously +submitted work, or the provided timeout has been reached. This method +should be called after close to provide a graceful shutdown.

+

This method is called synchronously by the stream processor during +assignment revocation, and blocks the assignment from being released +until this function exits, allowing any work in progress to be +completed and committed before the continuing the rebalancing +process.

+
+ +
+
+abstract poll() None
+

Poll the processor to check on the status of asynchronous tasks or +perform other scheduled work.

+

This method is called on each consumer loop iteration, so this method +should not be used to perform work that may block for a significant +amount of time and block the progress of the consumer or exceed the +consumer poll interval timeout.

+

This method may raise exceptions that were thrown by asynchronous +tasks since the previous call to poll.

+
+ +
+
+abstract submit(message: Message[TStrategyPayload]) None
+

Submit a message for processing.

+

Messages may be processed synchronously or asynchronously, depending +on the implementation of the processing strategy. Callers of this +method should not assume that this method returning successfully +implies that the message was successfully processed.

+

If the processing strategy is unable to accept a message (due to it +being at or over capacity, for example), this method will raise a +MessageRejected exception.

+
+ +
+
+abstract terminate() None
+

Close the processing strategy immediately, abandoning any work in +progress. No more messages should be accepted by the instance after +this method has been called.

+
+ +
+ +
+
+class arroyo.processing.strategies.abstract.ProcessingStrategyFactory(*args, **kwds)
+

Bases: ABC, Generic[TStrategyPayload]

+

A ProcessingStrategyFactory is used to wrap a series of +ProcessingStrategy steps, and calls create_with_partitions +to instantiate the ProcessingStrategy on partition assignment +or partition revocation if the strategy needs to be recreated.

+
+
+abstract create_with_partitions(commit: Commit, partitions: Mapping[Partition, int]) ProcessingStrategy[TStrategyPayload]
+

Instantiate and return a ProcessingStrategy instance.

+
+
Parameters:
+
    +
  • commit – A function that accepts a mapping of Partition instances to offset values that should be committed.

  • +
  • partitions – A mapping of a Partition to it’s most recent offset.

  • +
+
+
+
+ +
+
+shutdown() None
+

Custom code to execute when the StreamProcessor shuts down entirely.

+

Note that this code will also be executed on crashes of the strategy.

+
+ +
+ +
+
+

Messages

+
+
+class arroyo.types.BaseValue(*args, **kwds)
+
+
+property committable: Mapping[Partition, int]
+
+ +
+
+property payload: TMessagePayload
+
+ +
+
+replace(value: TReplaced) BaseValue[TReplaced]
+
+ +
+
+property timestamp: datetime | None
+
+ +
+ +
+
+class arroyo.types.BrokerValue(payload: TMessagePayload, partition: Partition, offset: int, timestamp: datetime)
+

A payload received from the consumer or producer after it is done producing. +Partition, offset, and timestamp values are present.

+
+
+property committable: Mapping[Partition, int]
+
+ +
+
+property next_offset: int
+
+ +
+
+offset: int
+
+ +
+
+partition: Partition
+
+ +
+
+property payload: TMessagePayload
+
+ +
+
+replace(value: TReplaced) BaseValue[TReplaced]
+
+ +
+
+timestamp: datetime
+
+ +
+ +
+
+class arroyo.types.Commit(*args, **kwargs)
+
+ +
+
+class arroyo.types.FilteredPayload
+
+ +
+
+class arroyo.types.Message(value: BaseValue[TMessagePayload])
+

Contains a payload and partitions to be committed after processing. +Can either represent a single message from a Kafka broker (BrokerValue) +or something else, such as a number of messages grouped together for a +batch processing step (Payload).

+
+
+property committable: Mapping[Partition, int]
+
+ +
+
+property payload: TMessagePayload
+
+ +
+
+property payload_unfiltered: TMessagePayload
+
+ +
+
+replace(payload: TReplaced) Message[TReplaced]
+
+ +
+
+property timestamp: datetime | None
+
+ +
+
+value: BaseValue[TMessagePayload]
+
+ +
+ +
+
+class arroyo.types.Partition(topic: 'Topic', index: 'int')
+
+
+index: int
+
+ +
+
+topic: Topic
+
+ +
+ +
+
+class arroyo.types.Topic(name: 'str')
+
+
+name: str
+
+ +
+ +
+
+class arroyo.types.Value(payload: TMessagePayload, committable: Mapping[Partition, int], timestamp: datetime | None = None)
+

Any other payload that may not map 1:1 to a single message from a +consumer. May represent a batch spanning many partitions.

+
+
+property committable: Mapping[Partition, int]
+
+ +
+
+property payload: TMessagePayload
+
+ +
+
+replace(value: TReplaced) BaseValue[TReplaced]
+
+ +
+
+property timestamp: datetime | None
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/strategies/noop.html b/strategies/noop.html new file mode 100644 index 00000000..27fb770e --- /dev/null +++ b/strategies/noop.html @@ -0,0 +1,182 @@ + + + + + Noop - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Noop

+
+
+class arroyo.processing.strategies.noop.Noop
+

Noop strategy that takes a message and does nothing.

+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/strategies/produce.html b/strategies/produce.html new file mode 100644 index 00000000..d484a919 --- /dev/null +++ b/strategies/produce.html @@ -0,0 +1,193 @@ + + + + + Produce - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Produce

+
+
+class arroyo.processing.strategies.produce.Produce(producer: Producer[TStrategyPayload], topic: Topic, next_step: ProcessingStrategy[FilteredPayload | TStrategyPayload], max_buffer_size: int = 10000)
+

This strategy can be used to produce Kafka messages to a destination topic. A typical use +case could be to consume messages from one topic, apply some transformations and then output +to another topic.

+

For each message received in the submit method, it attempts to produce a single Kafka message +in a thread. If there are too many pending futures, we MessageRejected will be raised to notify +stream processor to slow down.

+

On poll we check for completion of the produced messages. If the message has been successfully +produced then the message is submitted to the next step. If an error occured the exception will +be raised.

+

Important: The destination topic is always the topic passed into the constructor and not the +topic being referenced in the message itself (which typically refers to the original topic from +where the message was consumed from).

+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/strategies/reduce.html b/strategies/reduce.html new file mode 100644 index 00000000..e5349895 --- /dev/null +++ b/strategies/reduce.html @@ -0,0 +1,196 @@ + + + + + Reduce (Fold) - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Reduce (Fold)

+

Accumulate messages based on a custom accumulator function

+
+
+class arroyo.processing.strategies.reduce.Reduce(max_batch_size: int, max_batch_time: float, accumulator: Callable[[TResult, BaseValue[TPayload]], TResult], initial_value: Callable[[], TResult], next_step: ProcessingStrategy[TResult], compute_batch_size: Callable[[BaseValue[TPayload]], int] | None = None)
+

Accumulates messages until the max size or max time condition is hit. +The accumulator function is run on each message in the order it is received.

+

Once the “batch” is full, the accumulated value is submitted to the next step.

+

This strategy propagates MessageRejected exceptions from the +downstream steps if they are thrown.

+
+
Parameters:
+
    +
  • max_batch_size – How many messages should be reduced into one at maximum.

  • +
  • max_batch_time – How much time (in seconds) should be spent reducing +messages together before flushing the batch.

  • +
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/strategies/run_task.html b/strategies/run_task.html new file mode 100644 index 00000000..64841d40 --- /dev/null +++ b/strategies/run_task.html @@ -0,0 +1,184 @@ + + + + + Run Task - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Run Task

+
+
+class arroyo.processing.strategies.run_task.RunTask(function: Callable[[Message[TStrategyPayload]], TResult], next_step: ProcessingStrategy[FilteredPayload | TResult])
+

Basic strategy to run a custom processing function on a message.

+

The processing function provided can raise InvalidMessage to indicate that +the message is invalid and should be put in a dead letter queue.

+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/strategies/run_task_in_threads.html b/strategies/run_task_in_threads.html new file mode 100644 index 00000000..05450363 --- /dev/null +++ b/strategies/run_task_in_threads.html @@ -0,0 +1,191 @@ + + + + + Run Task in Threads - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Run Task in Threads

+
+
+class arroyo.processing.strategies.run_task_in_threads.RunTaskInThreads(processing_function: Callable[[Message[TStrategyPayload]], TResult], concurrency: int, max_pending_futures: int, next_step: ProcessingStrategy[FilteredPayload | TResult])
+

This strategy can be used to run IO-bound tasks in parallel.

+

The user specifies a processing function (a callable that takes a message). For each message received +in the submit method, it runs that processing function. Once completed, the message is submitted +to the next step (with the payload containing the result of the processing function).

+

Since the processing function will be run in threads, avoid using objects which can be modified +by different threads or protect it using locks.

+

If there are too many pending futures, we MessageRejected will be raised to notify the stream processor +to slow down.

+

On poll we check for completion of futures. If processing is done, we submit to the next step. +If an error occured the original exception will be raised.

+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/strategies/run_task_with_multiprocessing.html b/strategies/run_task_with_multiprocessing.html new file mode 100644 index 00000000..db62818e --- /dev/null +++ b/strategies/run_task_with_multiprocessing.html @@ -0,0 +1,306 @@ + + + + + Run Task with Multiprocessing - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Run Task with Multiprocessing

+
+
+class arroyo.processing.strategies.run_task_with_multiprocessing.RunTaskWithMultiprocessing(function: Callable[[Message[TStrategyPayload]], TResult], next_step: ProcessingStrategy[FilteredPayload | TResult], max_batch_size: int, max_batch_time: float, pool: MultiprocessingPool, input_block_size: int | None = None, output_block_size: int | None = None, max_input_block_size: int | None = None, max_output_block_size: int | None = None)
+

Run a function in parallel across messages using subprocesses.

+

RunTaskWithMultiprocessing uses the multiprocessing stdlib module to +transform messages in parallel.

+
+
Parameters:
+
    +
  • function – The function to use for transforming.

  • +
  • next_step – The processing strategy to forward transformed messages to.

  • +
  • max_batch_size – Wait at most for this many messages before “closing” a batch.

  • +
  • max_batch_time – Wait at most for this many seconds before closing a batch.

  • +
  • pool – The multiprocessing pool to use for parallel processing. The same pool +instance can be re-used each time RunTaskWithMultiprocessing is created on +rebalance.

  • +
  • input_block_size

    For each subprocess, a shared memory buffer of +input_block_size is allocated. This value should be at least +message_size * max_batch_size large, where message_size is the expected +average message size.

    +

    If the value is too small, the batch is implicitly broken up. In that +case, the +arroyo.strategies.run_task_with_multiprocessing.batch.input.overflow +metric is emitted.

    +

    If the value is set to None, the input_block_size is automatically +adjusted to adapt to traffic. Keep in mind that this is a rather +experimental feature and less productionized than explicitly setting a +value.

    +

  • +
  • output_block_size

    Size of the shared memory buffer used to store +results. Like with input data, the batch is implicitly broken up on +overflow, and +arroyo.strategies.run_task_with_multiprocessing.batch.output.overflow +metric is incremented.

    +

    Like with input_block_size, the value can be set to None to enable +automatic resizing.

    +

  • +
  • max_input_block_size – If automatic resizing is enabled, this sets an +upper limit on how large those blocks can get.

  • +
  • max_output_block_size – Same as max_input_block_size but for output +blocks.

  • +
+
+
+
+

Number of processes

+

The metric +arroyo.strategies.run_task_with_multiprocessing.batches_in_progress +shows you how many processes arroyo is able to effectively use at any given +point.

+

The metric arroyo.strategies.run_task_with_multiprocessing.processes +shows how many processes arroyo was configured with.

+

If those two metrics don’t line up, your consumer is not bottlenecked on +number of processes. That’s a good thing, you want to have some reserve +capacity. But it means that increasing num_processes will not make your +consumer faster.

+
+
+

Batching

+

Arroyo sends messages in batches to subprocesses. max_batch_size and max_batch_time +should be tweaked for optimal performance. You can observe the effect in the following metrics:

+
    +
  • arroyo.strategies.run_task_with_multiprocessing.batch.size.msg: The number of messages per batch.

  • +
  • arroyo.strategies.run_task_with_multiprocessing.batch.size.bytes: The number of bytes used per batch.

  • +
+

The cost of batches (locking, synchronization) generally amortizes with +increased batch sizes. Too small batches, and this strategy will spend a +lot of time synchronizing between processes. Too large batches, however, +can cause your consumer to not use all processes effectively, as a lot of +time may be spent waiting for batches to fill up.

+

If batch.size.msg is flat (as in, it’s a perfectly straight line at a +constant), you are hitting max_batch_size. If batch.size.bytes is +flat, you are hitting input buffer overflow (see next section). If neither +are flat, you are hitting max_batch_time.

+
+
+

Input and output buffers

+

You want to keep an eye on these metrics:

+
    +
  1. arroyo.strategies.run_task_with_multiprocessing.batch.input.overflow

  2. +
  3. arroyo.strategies.run_task_with_multiprocessing.batch.output.overflow

  4. +
  5. arroyo.strategies.run_task_with_multiprocessing.batch.backpressure

  6. +
+

If batch.input.overflow is emitted at all, arroyo ran out of memory for +batching and started breaking up your batches into smaller ones. You want +to increase input_block_size in response. Note that when you do this, +you may have to re-tweak max_batch_size and max_batch_time, as you +were never hitting those configured limits before. Input overflow is not +really all that expensive in Arroyo, but since it affects how batching +works it can still make performance tuning of your consumer more confusing. +Best to avoid it anyway.

+

If batch.output.overflow is emitted at all, arroyo ran out of memory +when fetching the data from subprocesses, and so the response from +subprocesses to the main processes is chunked. Output overflow is very +expensive, and you want to avoid it. Increase output_block_size in +response.

+

If batch.backpressure is continuously emitted, you are not bottlenecked +on multiprocessing at all, but instead the next strategy can’t keep up and +is applying backpressure. You can likely reduce num_processes and won’t +notice a performance regression.

+
+
+

How to tune your consumer

+

Note that it doesn’t make sense to fix output overflow without fixing input +overflow first. If you increase output block size to get rid of output +overflows, then increase input block size, your effective batch size may +increase to a point where you encounter output overflow again. If you +encounter a lot of issues at once, best to fix them in this order:

+
    +
  1. First, tune input_block_size to fix input overflow. This will +increase average/effective batch size. Alternatively, set it to None +(default) to let arroyo auto-tune it.

  2. +
  3. Then, tune max_batch_size and max_batch_time so that you get the +highest throughput. Test this by running your consumer on a backlog of +messages and look at consumer offset rate (vs broker/total offset rate), +or time it takes to get consumer lag back to normal. For as long as you +have enough RAM, increment it in large steps (like 2x) and fine-tune +afterwards.

  4. +
  5. Then, tune output_block_size to fix output overflow. If in your +previous tests there was a lot of output overflow, this will remove a lot +of CPU load from your consumer and potentially also increase throughput.

  6. +
  7. Now take a look at the batch.backpressure metric. If it is emitted, +you need to optimize the next strategy (next_step) because that’s what +you’re bottlenecked on. If it is not emitted, you may need to increase +num_processes or increase batch size, so that +RunTaskWithMultiprocessing itself is not the bottleneck.

  8. +
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/strategies/unfold.html b/strategies/unfold.html new file mode 100644 index 00000000..96fa3782 --- /dev/null +++ b/strategies/unfold.html @@ -0,0 +1,190 @@ + + + + + Unfold - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

Unfold

+

Generates a sequence of messages from a single message based on a custom generator function

+
+
+class arroyo.processing.strategies.unfold.Unfold(generator: Callable[[TInput], Iterable[BaseValue[TOutput]]], next_step: ProcessingStrategy[FilteredPayload | TOutput])
+

Unfold receives a message and explodes it to generate a collection of +messages submitting them one by one to the next step. The generated +messages are created according to the generator function provided by the user.

+

The generator function provided must return an iterable (i.e. a class that +implements __iter__ ).

+

If this step receives a MessageRejected exception from the next +step it keeps the remaining messages and attempts to submit +them on subsequent calls to poll

+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/what_for.html b/what_for.html new file mode 100644 index 00000000..27888a6c --- /dev/null +++ b/what_for.html @@ -0,0 +1,432 @@ + + + + + What is Arroyo for? - Arroyo documentation + + + + + + + + + + +
+
+
+ + + + Arroyo + +
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+

What is Arroyo for?

+

Arroyo is a library for writing high-throughput, testable kafka +consumers and producers. This document attempts to outline the +intricacies of writing such consumers.

+
+

Goals

+
    +
  1. Make it easy to build consumers that provide delivery guarantees

  2. +
  3. Make it easy to write high-throughput kafka consumers

  4. +
  5. Make it easy to write reliable, high throughput kafka producers

  6. +
  7. Abstract away rebalancing such that users of the library do not have +to worry about it

  8. +
  9. Make it possible to test kafka consumer logic without having to +reproduce the entire kafka environment

  10. +
  11. Provide a way for the application logic to signal backpressure

  12. +
+
+
+

Why Simple Doesn’t Cut It

+

When visualizing event-driven architecture, Kafka is viewed as an +abstract queue with groups of producers pushing to it, and consumers +consuming from it (as in the diagram below).

+
+ graph TD + Producer --> Kafka_Topic + Kafka_Topic --> Consumer + Consumer --> Destination +

A more accurate model is that kafka is like a log file which is +persistent and there are offsets of the file that different consumers +have read or not read.

+

The most simple kafka consumer looks something like this:

+
from confluent_kafka import Consumer
+
+conf = {
+    "bootstrap.servers": "localhost:9092",
+    "group.id": "my_group",
+    "auto.offset.reset": "latest",
+}
+
+consumer = Consumer(conf)
+consumer.subscribe(["my_topic"])
+
+while True:
+    message = consumer.poll()
+    send_to_destination(process_message(message))
+
+
+

This simple consumer would not satisfy the goals mentioned at the top of +this page. The following subsections will explain why

+
+
+

Providing delivery guarantees

+

By default, a consumer in the confluent_kafka library will +auto-commit on poll. To understand what it means to commit to a kafka +topic, see the Appendix. This can lead to the following issue:

+
# get message from kafka, commit immediately
+message = consumer.poll()
+# ❗❗❗ throws exception due to a network issue
+send_to_destination(process_message(message))
+# this message is now lost and we're on to the next one
+
+
+

This can be fixed by only committing after we know that the message has +reached its destination in the following way:

+
# add this value to the config:
+"enable.auto.commit": "false"
+# -------
+message = consumer.poll(timeout=0)
+send_to_destination(process_message(message))
+consumer.commit(message.offset())
+
+
+
+
+

High Throughput

+

The previous section has allowed us to not commit messages that are not +processed however committing every message severely hurts throughput. +Every call to commit is a network operation, it also makes the broker +persist and replicate the information. If we can reduce the number of +commit calls, our throughput can be much higher. And so we commit in +batches

+
# this code is purely descriptive.
+# We have to commit to each partition separately
+# but that code is not helpful for this example
+message = consumer.poll(timeout=0)
+batch.append(process_message(message))
+if len(batch) == batch_size:
+    consumer.commit(offsets=[m.offset() for m in batch])
+
+
+

This will get us faster throughput however we are currently hand-waving +away how we send the message to its destination

+
+
+

Reliable High Throughput Batched Producers

+

Producing to Kafka reliably and at high throughput is not a simple +operation. Here is how a simple Kafka Producer looks in code:

+
from confluent_kafka import Producer
+
+conf = {
+  "bootstrap.servers": "localhost:9092",
+}
+producer = Producer(conf)
+def send_to_destination(message):
+    # ❗ This does not do what it says
+    # it writes to a buffer
+    producer.produce("destination_topic", message)
+    # this will actually block until the messages are produced
+    # calling this after produce every time is very expensive,
+    # how often we flush has high impacts on the producer throughput
+    producer.flush()
+
+
+

At a high level, the producer is actually buffering the messages +produced to the topic

+_images/kafka_producer.png +

A kafka producer writes to an internal buffer. This batches the IO +(good) but you don’t know when it will ever make it to the +destination

+

In order to allow for reliability of transmission, the +confluent_kafka library provides a callback to +produce +like so

+
def delivery_callback(error, message):
+    # do something here to make sure your message is in the state
+    # you want it to be
+
+producer.produce("destination_topic", message, on_delivery=delivery_callback)
+
+
+
+
+

Dealing With Rebalancing

+
+

What is Rebalancing

+

A kafka topic is divided into n partitions, each partition can be +consumed by exactly one consumer per consumer +group. A consumer can +consume multiple partitions

+
+_images/consumer_groups.png +
+
+
+

When Rebalancing Can Happen

+

Rebalancing can happen due to:

+
    +
  • An addition or removal of a consumer to a consumer group

    +
      +
    • (Every deploy does this)

    • +
    +
  • +
  • A rebalance being kicked off manually

  • +
  • A consumer pod dies and now its partition needs to be re-assigned

  • +
  • Whenever the broker decides it’s a good idea (it can happen at any +time)

  • +
  • TODO: More things?

  • +
+
+
+

How Rebalancing Affects a Consumer

+

Rebalancing is annoying to handle for a consumer that processes batches, +imagine the following scenario:

+
+ sequenceDiagram + Broker->>Consumer: message + activate Consumer + note right of Consumer: start building batch + Broker->>Consumer: message + Broker->>Consumer: Revoke Partition + deactivate Consumer + Consumer->>Broker: commit batch + note left of Broker: Received commit from revoked Consumer! +

Once a partition is revoked for a consumer, it cannot commit to it. This +is bad news for the batch that the consumer has built up. Each consumer +has different requirements but a decision has to be made as to whether +to flush the batch or to discard its work and let the next consumer +assigned to this partition pick it up. The rebalancing behavior can be +customized by providing an on_revoke callback to the consumer when +subscribing.

+
from confluent_kafka import Consumer
+
+conf = {
+    "bootstrap.servers": "localhost:9092",
+    "group.id": "my_group",
+    "auto.offset.reset": "latest",
+}
+
+def flush_current_batch(consumer, partitions):
+    # flush the current batch
+    pass
+
+consumer = Consumer(conf)
+consumer.subscribe(["my_topic"], on_revoke=flush_current_batch)
+
+
+
+
+
+

librdkafka’s Callback Hell

+

librdkafka uses callbacks as a core mechanic for control flow. A few +such examples have been mentioned in this document already. What is not +clear however, is that callbacks are only called when ``poll`` is +called

+

This means that:

+
    +
  • this line could possibly do a lot of work:

  • +
+
# any scheduled callbacks will run within this call
+message = consumer.poll()
+
+
+
    +
  • No callbacks will be invoked until the consumer or producer call +poll again (for their respective callbacks)

  • +
  • poll has to be called periodically on a consumer otherwise the +broker will kick the consumer out of the consumer group

    +
      +
    • The handling of that revocation won’t happen until poll is +called

    • +
    +
  • +
+
+
+

Conclusion

+

There are many intricacies and gotchas to writing high performant, +reliable kafka consumers. This document does not outline all of them but +all of what is outlined here should be kept in mind when designing any +kafka consumer library.

+
+
+

Appendix

+
+
+

Committing to a Kafka Topic

+

A consumer comitting to a topic signals to the broker that this message +has been processed. When poll is called next by that consumer, it +will return the next message.

+

API +Doc

+
+
+

What is a Kafka Consumer Group

+

https://www.educba.com/kafka-consumer-group/

+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file