From bbd444dca17977246dbe9bc06ebc3a05ab2fce2a Mon Sep 17 00:00:00 2001 From: Klaas van Schelven Date: Wed, 20 Nov 2024 12:09:45 +0100 Subject: [PATCH] SDK: platform-specific notes for PHP, JS --- bugsink/settings/default.py | 1 - .../templates/projects/project_sdk_setup.html | 49 +++++------ .../project_sdk_setup_javascript.html | 84 +++++++++++++++++++ .../projects/project_sdk_setup_php.html | 81 ++++++++++++++++++ .../projects/project_sdk_setup_python.html | 80 ++++++++++++++++++ projects/urls.py | 1 + projects/views.py | 27 ++---- requirements.txt | 1 - theme/static/css/dist/styles.css | 2 +- theme/static_src/tailwind.config.js | 2 +- theme/templatetags/code.py | 40 +++++++++ 11 files changed, 314 insertions(+), 54 deletions(-) create mode 100644 projects/templates/projects/project_sdk_setup_javascript.html create mode 100644 projects/templates/projects/project_sdk_setup_php.html create mode 100644 projects/templates/projects/project_sdk_setup_python.html create mode 100644 theme/templatetags/code.py diff --git a/bugsink/settings/default.py b/bugsink/settings/default.py index 59ff7b8..21096d9 100644 --- a/bugsink/settings/default.py +++ b/bugsink/settings/default.py @@ -56,7 +56,6 @@ INSTALLED_APPS = [ 'tailwind', # As currently set up, this is also needed in production (templatetags) 'admin_auto_filters', - 'sdkconf', ] BUGSINK_APPS = [ diff --git a/projects/templates/projects/project_sdk_setup.html b/projects/templates/projects/project_sdk_setup.html index 2e3607c..3212bff 100644 --- a/projects/templates/projects/project_sdk_setup.html +++ b/projects/templates/projects/project_sdk_setup.html @@ -1,7 +1,7 @@ {% extends "base.html" %} -{% load static %} +{% load static code %} -{% block title %}Set up your SDK · {{ site_title }}{% endblock %} +{% block title %}Connect your application · {{ site_title }}{% endblock %} {% block content %} @@ -9,49 +9,38 @@
-

Set up your SDK

+

Connect your application

Connect your application to Bugsink to start tracking errors. - Bugsink is compatible with the Sentry SDK. A basic setup is the following: + Bugsink is compatible with the Sentry SDKs. + Use the following DSN to connect your application to Bugsink:
+{% code %}:::text +{{ dsn }} +{% endcode %} -

Step 1: Install the SDK

- +

Language-specific instructions

- {{ install_text }} + Pick your language to get specific instructions on how to set up the SDK. + + +
- - {{ install }} - -

Step 2: Initialize the SDK

-
- Initialize and configure the SDK with your DSN. Add the following at the start of your application code: -
- - {{ conf }} - -

Step 3: Verify the setup

- -
- To verify that everything is working, raise an exception on purpose and check that it appears in Bugsink. - Put this code somewhere after the configuration: -
- - {{ verify }} - -
- Your event should now appear in the list of open issues. + Not listed? It will probably work anyway! Just use the DSN above, and let us know if you run into any issues (or not).

Further reading

- For more information on how to use the SDK, check the full documentation. + For more information on how to use the SDK, check the Bugsink-specific SDK recommendations.
-
diff --git a/projects/templates/projects/project_sdk_setup_javascript.html b/projects/templates/projects/project_sdk_setup_javascript.html new file mode 100644 index 0000000..1b79e19 --- /dev/null +++ b/projects/templates/projects/project_sdk_setup_javascript.html @@ -0,0 +1,84 @@ +{% extends "base.html" %} +{% load static code %} + +{% block title %}Set up your SDK · {{ site_title }}{% endblock %} + +{% block content %} + + +
+ +
+

Set up your SDK

+ +
+ Connect your JavaScript application to Bugsink to start tracking errors. + Bugsink is compatible with the Sentry SDK. + Detailed instructions per framework are in the Sentry SDK Documentation. In the below we provide an overview, zoom in on the differences between Bugsink and Sentry, and provide a snippet with the correct DSN set. +
+ +

Step 1: Install the SDK

+ +
+ Install the SDK by including a script tag, or by using a package manager such as npm or yarn. Note that the SDK to use depends on your framework (e.g. React, Angular, Vue, etc.). + +{% code %}:::text +npm install @sentry/your-js-flavor --save +{% endcode %} +
+ +

Step 2: Initialize the SDK

+ +
+ Initialize and configure the SDK with your DSN. Use the configuration snippet that applies to your framework, + and replace the Sentry.init call with the call below: +
+ +{% code %}:::javascript +// use the correct import statement for your framework, like: + +// import * as Sentry from "@sentry/browser"; +// const Sentry = require("@sentry/node"); +// import * as Sentry from "@sentry/angular"; + +Sentry.init({ + dsn: "{{ dsn }}", + + // Alternatively, use `process.env.npm_package_version` for a dynamic release version + // if your build tool supports it. + release: "my-project-name@...", + + integrations: [], + tracesSampleRate: 0, +{% comment %} sendDefaultPii: true, // not actually implemented yet, see https://github.com/getsentry/sentry-javascript/issues/5347 +{% endcomment %}}); +{% endcode %} + +

Step 3: Verify the setup

+ +
+ To verify that everything is working, raise an exception on purpose and check that it appears in Bugsink. + A good location for this depends on your framework, but a good place is in the main entry point of your application. + (Don't use the devtools console, because it will not be caught by the SDK). +
+ +{% code %}:::javascript +// Put something like this in the handler of a button click, on a timeout, or similar. +throw new Error("Error Thrown on purpose to send it to Bugsink"); +{% endcode %} + +
+ Your event should now appear in the list of open issues. +
+ +

Further reading

+ +
+ For more information on how to use the SDK, check the Bugsink-specific SDK recommendations. +
+ + +
+
+ +{% endblock %} diff --git a/projects/templates/projects/project_sdk_setup_php.html b/projects/templates/projects/project_sdk_setup_php.html new file mode 100644 index 0000000..f56768f --- /dev/null +++ b/projects/templates/projects/project_sdk_setup_php.html @@ -0,0 +1,81 @@ +{% extends "base.html" %} +{% load static code %} + +{% block title %}Set up your SDK · {{ site_title }}{% endblock %} + +{% block content %} + + +
+ +
+

Set up your SDK

+ +
+ Connect your PHP application to Bugsink to start tracking errors. + Bugsink is compatible with the Sentry SDK. + Note that the instructions for Laravel and Symfony are quite different from plain PHP. + In the below we provide an overview, zoom in on the differences between Bugsink and Sentry, and provide a snippet with the correct DSN set. +
+ +

Step 1: Install the SDK

+ +
+ Install the SDK using Composer. Run either of the following commands: + +{% code %}:::text +PICK ONE: +composer require sentry/sentry +composer require sentry/sentry-laravel +composer require sentry/sentry-symfony +{% endcode %} +
+ +

Step 2: Configure the SDK with your DSN

+ +
+ The steps for initialization/configuration differ between the different frameworks. Refer to the relevant + document. +
+
+ Configure the SDK without tracing and profiling, as Bugsink does not support these features. +
+
+ Initialize and configure the SDK with your DSN: +
+ +{% code %}:::php +{{ dsn }} +{% endcode %} + +

Step 3: Verify the setup

+ +
+ To verify that everything is working, raise an exception on purpose and check that it appears in Bugsink. + How to do this depends on the framework you are using, as per the documentation. Here's an example for plain PHP: +
+ +{% code %}:::php +iWantThisToFailSoBugsinkShowsIt(); +} catch (\Throwable $exception) { + \Sentry\captureException($exception); +} +?>{% endcode %} + +
+ Your event should now appear in the list of open issues. +
+ +

Further reading

+ +
+ For more information on how to use the SDK, check the Bugsink-specific SDK recommendations. +
+ + +
+
+ +{% endblock %} diff --git a/projects/templates/projects/project_sdk_setup_python.html b/projects/templates/projects/project_sdk_setup_python.html new file mode 100644 index 0000000..bafe8a1 --- /dev/null +++ b/projects/templates/projects/project_sdk_setup_python.html @@ -0,0 +1,80 @@ +{% extends "base.html" %} +{% load static code %} + +{% block title %}Connect your Python Application · {{ site_title }}{% endblock %} + +{% block content %} + + +
+ +
+

Connect your Python Application

+ +
+ Connect your Python application to Bugsink to start tracking errors. + Bugsink is compatible with the Sentry SDK. A basic setup is the following: +
+ +

Step 1: Install the SDK

+ +
+ Install the SDK using pip: +
+ +{% code %}:::text +pip install sentry-sdk +{% endcode %} + +

Step 2: Initialize the SDK

+ +
+ Initialize and configure the SDK with your DSN. Add the following at the start of your application code: +
+ +{% code %}:::python +import sentry_sdk + +sentry_sdk.init( + "{{ dsn }}", + + send_default_pii=True, + max_request_body_size="always", + + # Setting up the release is highly recommended. The SDK will try to + # infer it, but explicitly setting it is more reliable: + # release=..., + + traces_sample_rate=0, +) +{% endcode %} + +

Step 3: Verify the setup

+ +
+ To verify that everything is working, raise an exception on purpose and check that it appears in Bugsink. + +
+
+ Put this code somewhere in your application where it can easily be triggered and then trigger it (don't use an interactive shell since the SDK will ignore exceptions raised in it): +
+ +{% code %}:::python +raise Exception("Raised Exception on purpose to send it to Bugsink") +{% endcode %} + +
+ Your event should now appear in the list of open issues. +
+ +

Further reading

+ +
+ For more information on how to use the SDK, check the Bugsink-specific SDK recommendations. +
+ + +
+
+ +{% endblock %} diff --git a/projects/urls.py b/projects/urls.py index 51b3ff3..6b08424 100644 --- a/projects/urls.py +++ b/projects/urls.py @@ -20,4 +20,5 @@ urlpatterns = [ path('/members/settings//', project_member_settings, name="project_member_settings"), path('/sdk-setup/', project_sdk_setup, name="project_sdk_setup"), + path('/sdk-setup//', project_sdk_setup, name="project_sdk_setup_platform"), ] diff --git a/projects/views.py b/projects/views.py index d4375b4..12eef43 100644 --- a/projects/views.py +++ b/projects/views.py @@ -1,10 +1,5 @@ from datetime import timedelta -from pygments import highlight -from pygments.lexers import PythonLexer, TextLexer -from pygments.formatters import HtmlFormatter - -from django.template.loader import get_template from django.shortcuts import render from django.db import models from django.shortcuts import redirect @@ -15,7 +10,6 @@ from django.contrib import messages from django.contrib.auth import logout from django.urls import reverse from django.utils import timezone -from django.utils.safestring import mark_safe from users.models import EmailVerification from teams.models import TeamMembership, Team, TeamRole @@ -385,16 +379,8 @@ def project_members_accept(request, project_pk): return render(request, "projects/project_members_accept.html", {"project": project, "membership": membership}) -def _sdk_code(part, context, lexer): - template = get_template("sdkconf/python/%s" % part) - code = template.render(context) - - return mark_safe( - highlight(code, lexer, HtmlFormatter()).replace("highlight", "p-4 mt-4 bg-slate-50 syntax-coloring")) - - @atomic_for_request_method -def project_sdk_setup(request, project_pk): +def project_sdk_setup(request, project_pk, platform=""): project = Project.objects.get(id=project_pk) if not request.user.is_superuser and not ProjectMembership.objects.filter(project=project, user=request.user, @@ -404,10 +390,11 @@ def project_sdk_setup(request, project_pk): # NOTE about lexers:: I have bugsink/pyments_extensions; but the platforms mentioned there don't necessarily map to # what I will make selectable here. "We'll see" whether yet another lookup dict will be needed. - return render(request, "projects/project_sdk_setup.html", { + assert platform in ["", "python", "javascript", "php"] + + template_name = "projects/project_sdk_setup%s.html" % ("_" + platform if platform else "") + + return render(request, template_name, { "project": project, - "install_text": get_template("sdkconf/python/install_text").render({}), - "install": _sdk_code("install", {}, TextLexer()), - "conf": _sdk_code("conf", {"dsn": project.dsn}, PythonLexer()), - "verify": _sdk_code("verify", {}, PythonLexer()), + "dsn": project.dsn, }) diff --git a/requirements.txt b/requirements.txt index 9c144c9..8ff556b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,3 @@ monofy==1.1.* user_agents fastjsonschema verbose_csrf_middleware -sdkconf diff --git a/theme/static/css/dist/styles.css b/theme/static/css/dist/styles.css index a6b8a79..9f414ce 100644 --- a/theme/static/css/dist/styles.css +++ b/theme/static/css/dist/styles.css @@ -1 +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.13 | 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:IBM Plex Sans,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:IBM Plex Mono,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]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],input:where(:not([type])),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,input:where(:not([type])):focus,select:focus,textarea:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple],[size]:where(select:not([size="1"])){background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=checkbox]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=radio]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=radio]:checked:focus,[type=radio]:checked:hover{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}@media (forced-colors:active) {[type=checkbox]:indeterminate{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.visible{visibility:visible}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.left-1\/2{left:50%}.float-right{float:right}.m-1{margin:.25rem}.m-4{margin:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-0{margin-left:0}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-auto{margin-left:auto}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.size-6{width:1.5rem;height:1.5rem}.size-8{width:2rem;height:2rem}.h-12{height:3rem}.h-32{height:8rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-screen{height:100vh}.w-1\/3{width:33.333333%}.w-1\/4{width:25%}.w-10\/12{width:83.333333%}.w-12{width:3rem}.w-128{width:32rem}.w-2\/3{width:66.666667%}.w-3\/4{width:75%}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.max-w-4xl{max-width:56rem}.flex-\[2_1_96rem\]{flex:2 1 96rem}.flex-auto{flex:1 1 auto}.flex-none{flex:none}.border-collapse{border-collapse:collapse}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.-translate-y-1\/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-y-1\/2{--tw-translate-y:-50%}.rotate-180{--tw-rotate:180deg}.rotate-180,.transform{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))}.cursor-pointer{cursor:pointer}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-none{list-style-type:none}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.place-content-end{place-content:end}.content-start{align-content:flex-start}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.self-stretch{align-self:stretch}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-scroll{overflow-y:scroll}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-e-md{border-start-end-radius:.375rem;border-end-end-radius:.375rem}.rounded-s-md{border-start-start-radius:.375rem;border-end-start-radius:.375rem}.border{border-width:1px}.border-2{border-width:2px}.border-b-2{border-bottom-width:2px}.border-b-4{border-bottom-width:4px}.border-l-2{border-left-width:2px}.border-r-2{border-right-width:2px}.border-t-2{border-top-width:2px}.border-dotted{border-style:dotted}.border-cyan-500{--tw-border-opacity:1;border-color:rgb(6 182 212/var(--tw-border-opacity))}.border-cyan-800{--tw-border-opacity:1;border-color:rgb(21 94 117/var(--tw-border-opacity))}.border-red-800{--tw-border-opacity:1;border-color:rgb(153 27 27/var(--tw-border-opacity))}.border-slate-200{--tw-border-opacity:1;border-color:rgb(226 232 240/var(--tw-border-opacity))}.border-slate-300{--tw-border-opacity:1;border-color:rgb(203 213 225/var(--tw-border-opacity))}.border-slate-400{--tw-border-opacity:1;border-color:rgb(148 163 184/var(--tw-border-opacity))}.border-slate-50{--tw-border-opacity:1;border-color:rgb(248 250 252/var(--tw-border-opacity))}.border-slate-500{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity))}.border-yellow-200{--tw-border-opacity:1;border-color:rgb(254 240 138/var(--tw-border-opacity))}.bg-cyan-100{--tw-bg-opacity:1;background-color:rgb(207 250 254/var(--tw-bg-opacity))}.bg-cyan-200{--tw-bg-opacity:1;background-color:rgb(165 243 252/var(--tw-bg-opacity))}.bg-cyan-50{--tw-bg-opacity:1;background-color:rgb(236 254 255/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-slate-100{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity))}.bg-slate-50{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity))}.bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-slate-300{--tw-gradient-from:#cbd5e1 var(--tw-gradient-from-position);--tw-gradient-to:rgba(203,213,225,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.fill-cyan-500{fill:#06b6d4}.fill-slate-300{fill:#cbd5e1}.fill-slate-500{fill:#64748b}.fill-slate-800{fill:#1e293b}.stroke-slate-300{stroke:#cbd5e1}.p-12{padding:3rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pl-1{padding-left:.25rem}.pl-12{padding-left:3rem}.pl-2{padding-left:.5rem}.pl-4{padding-left:1rem}.pl-6{padding-left:1.5rem}.pr-2{padding-right:.5rem}.pr-4{padding-right:1rem}.pr-6{padding-right:1.5rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.pt-4{padding-top:1rem}.pt-6{padding-top:1.5rem}.pt-8{padding-top:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.font-mono{font-family:IBM Plex Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-serif{font-family:ui-serif,Georgia,Cambria,Times New Roman,Times,serif}.text-2xl{font-size:1.5rem;line-height:2rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-normal{line-height:1.5}.tracking-normal{letter-spacing:0}.text-cyan-500{--tw-text-opacity:1;color:rgb(6 182 212/var(--tw-text-opacity))}.text-cyan-800{--tw-text-opacity:1;color:rgb(21 94 117/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.text-slate-300{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity))}.text-slate-800{--tw-text-opacity:1;color:rgb(30 41 59/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.underline{text-decoration-line:underline}.decoration-dotted{text-decoration-style:dotted}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}@font-face{font-display:swap;font-family:IBM Plex Sans;font-style:normal;font-weight:400;src:url(../../fonts/ibm-plex-sans-v19-cyrillic_cyrillic-ext_greek_latin_latin-ext_vietnamese-regular.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Sans;font-style:italic;font-weight:400;src:url(../../fonts/ibm-plex-sans-v19-cyrillic_cyrillic-ext_greek_latin_latin-ext_vietnamese-italic.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Sans;font-style:normal;font-weight:700;src:url(../../fonts/ibm-plex-sans-v19-cyrillic_cyrillic-ext_greek_latin_latin-ext_vietnamese-700.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Sans;font-style:italic;font-weight:700;src:url(../../fonts/ibm-plex-sans-v19-cyrillic_cyrillic-ext_greek_latin_latin-ext_vietnamese-700italic.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:normal;font-weight:400;src:url(../../fonts/ibm-plex-mono-v19-cyrillic_cyrillic-ext_latin_latin-ext_vietnamese-regular.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:italic;font-weight:400;src:url(../../fonts/ibm-plex-mono-v19-cyrillic_cyrillic-ext_latin_latin-ext_vietnamese-italic.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:normal;font-weight:700;src:url(../../fonts/ibm-plex-mono-v19-cyrillic_cyrillic-ext_latin_latin-ext_vietnamese-700.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:italic;font-weight:700;src:url(../../fonts/ibm-plex-mono-v19-cyrillic_cyrillic-ext_latin_latin-ext_vietnamese-700italic.woff2) format("woff2")}.dropdown{position:relative;display:inline-block}.dropdown-content-right{display:none;position:absolute;z-index:1;margin-left:auto;right:0}.dropdown-content-left{display:none;position:absolute;z-index:1;left:0}.dropdown:hover .dropdown-content-left,.dropdown:hover .dropdown-content-right{display:flex}.triangle-left{position:relative}.triangle-left:before{content:"";border-color:transparent #cbd5e1 transparent transparent;border-style:solid;border-width:9px 8px 9px 0;position:absolute;left:-8px;top:20px}.triangle-left:after{content:"";border-color:transparent #fff transparent transparent;border-style:solid;border-width:9px 8px 9px 0;position:absolute;left:-6px;top:20px}pre{line-height:125%}.syntax-coloring .c{color:#3d7b7b;font-style:italic}.syntax-coloring .err{border:1px solid red}.syntax-coloring .k{color:green;font-weight:700}.syntax-coloring .o{color:#666}.syntax-coloring .ch,.syntax-coloring .cm{color:#3d7b7b;font-style:italic}.syntax-coloring .cp{color:#9c6500}.syntax-coloring .c1,.syntax-coloring .cpf,.syntax-coloring .cs{color:#3d7b7b;font-style:italic}.syntax-coloring .gd{color:#a00000}.syntax-coloring .ge{font-style:italic}.syntax-coloring .ges{font-weight:700;font-style:italic}.syntax-coloring .gr{color:#e40000}.syntax-coloring .gh{color:navy;font-weight:700}.syntax-coloring .gi{color:#008400}.syntax-coloring .go{color:#717171}.syntax-coloring .gp{color:navy;font-weight:700}.syntax-coloring .gs{font-weight:700}.syntax-coloring .gu{color:purple;font-weight:700}.syntax-coloring .gt{color:#04d}.syntax-coloring .kc,.syntax-coloring .kd,.syntax-coloring .kn{color:green;font-weight:700}.syntax-coloring .kp{color:green}.syntax-coloring .kr{color:green;font-weight:700}.syntax-coloring .kt{color:#b00040}.syntax-coloring .m{color:#666}.syntax-coloring .s{color:#ba2121}.syntax-coloring .na{color:#687822}.syntax-coloring .nb{color:green}.syntax-coloring .nc{color:#00f;font-weight:700}.syntax-coloring .no{color:#800}.syntax-coloring .nd{color:#a2f}.syntax-coloring .ni{color:#717171;font-weight:700}.syntax-coloring .ne{color:#cb3f38;font-weight:700}.syntax-coloring .nf{color:#00f}.syntax-coloring .nl{color:#767600}.syntax-coloring .nn{color:#00f;font-weight:700}.syntax-coloring .nt{color:green;font-weight:700}.syntax-coloring .nv{color:#19177c}.syntax-coloring .ow{color:#a2f;font-weight:700}.syntax-coloring .w{color:#bbb}.syntax-coloring .mb,.syntax-coloring .mf,.syntax-coloring .mh,.syntax-coloring .mi,.syntax-coloring .mo{color:#666}.syntax-coloring .dl,.syntax-coloring .sa,.syntax-coloring .sb,.syntax-coloring .sc{color:#ba2121}.syntax-coloring .sd{color:#ba2121;font-style:italic}.syntax-coloring .s2{color:#ba2121}.syntax-coloring .se{color:#aa5d1f;font-weight:700}.syntax-coloring .sh{color:#ba2121}.syntax-coloring .si{color:#a45a77;font-weight:700}.syntax-coloring .sx{color:green}.syntax-coloring .sr{color:#a45a77}.syntax-coloring .s1{color:#ba2121}.syntax-coloring .ss{color:#19177c}.syntax-coloring .bp{color:green}.syntax-coloring .fm{color:#00f}.syntax-coloring .vc,.syntax-coloring .vg,.syntax-coloring .vi,.syntax-coloring .vm{color:#19177c}.syntax-coloring .il{color:#666}input[type=radio]{color:#06b6d4}.hover\:border-b-4:hover{border-bottom-width:4px}.hover\:border-slate-400:hover{--tw-border-opacity:1;border-color:rgb(148 163 184/var(--tw-border-opacity))}.hover\:bg-cyan-400:hover{--tw-bg-opacity:1;background-color:rgb(34 211 238/var(--tw-bg-opacity))}.hover\:bg-slate-100:hover{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.hover\:bg-slate-200:hover{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity))}.hover\:bg-slate-300:hover{--tw-bg-opacity:1;background-color:rgb(203 213 225/var(--tw-bg-opacity))}.focus\:border-cyan-500:focus{--tw-border-opacity:1;border-color:rgb(6 182 212/var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-cyan-200:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(165 243 252/var(--tw-ring-opacity))}.active\:ring:active{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}@media (min-width:768px){.md\:mb-8{margin-bottom:2rem}.md\:h-16{height:4rem}.md\:w-16{width:4rem}.md\:p-4{padding:1rem}.md\:p-8{padding:2rem}.md\:py-4{padding-top:1rem;padding-bottom:1rem}.md\:pb-16{padding-bottom:4rem}.md\:pl-24{padding-left:6rem}.md\:pr-24{padding-right:6rem}.md\:pt-24{padding-top:6rem}}@media (min-width:1024px){.lg\:w-5\/12{width:41.666667%}.lg\:flex-row-reverse{flex-direction:row-reverse}.lg\:pb-0{padding-bottom:0}}@media (min-width:1280px){.xl\:flex{display:flex}} \ No newline at end of file +*,: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.13 | 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:IBM Plex Sans,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:IBM Plex Mono,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]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],input:where(:not([type])),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,input:where(:not([type])):focus,select:focus,textarea:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple],[size]:where(select:not([size="1"])){background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=checkbox]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=radio]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=radio]:checked:focus,[type=radio]:checked:hover{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}@media (forced-colors:active) {[type=checkbox]:indeterminate{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.visible{visibility:visible}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.left-1\/2{left:50%}.float-right{float:right}.m-1{margin:.25rem}.m-4{margin:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-0{margin-left:0}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-auto{margin-left:auto}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.size-6{width:1.5rem;height:1.5rem}.size-8{width:2rem;height:2rem}.h-12{height:3rem}.h-32{height:8rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-screen{height:100vh}.w-1\/3{width:33.333333%}.w-1\/4{width:25%}.w-10\/12{width:83.333333%}.w-12{width:3rem}.w-128{width:32rem}.w-2\/3{width:66.666667%}.w-3\/4{width:75%}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.max-w-4xl{max-width:56rem}.flex-\[2_1_96rem\]{flex:2 1 96rem}.flex-auto{flex:1 1 auto}.flex-none{flex:none}.border-collapse{border-collapse:collapse}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.-translate-y-1\/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-y-1\/2{--tw-translate-y:-50%}.rotate-180{--tw-rotate:180deg}.rotate-180,.transform{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))}.cursor-pointer{cursor:pointer}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.place-content-end{place-content:end}.content-start{align-content:flex-start}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.self-stretch{align-self:stretch}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-scroll{overflow-y:scroll}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-e-md{border-start-end-radius:.375rem;border-end-end-radius:.375rem}.rounded-s-md{border-start-start-radius:.375rem;border-end-start-radius:.375rem}.border{border-width:1px}.border-2{border-width:2px}.border-b-2{border-bottom-width:2px}.border-b-4{border-bottom-width:4px}.border-l-2{border-left-width:2px}.border-r-2{border-right-width:2px}.border-t-2{border-top-width:2px}.border-dotted{border-style:dotted}.border-cyan-500{--tw-border-opacity:1;border-color:rgb(6 182 212/var(--tw-border-opacity))}.border-cyan-800{--tw-border-opacity:1;border-color:rgb(21 94 117/var(--tw-border-opacity))}.border-red-800{--tw-border-opacity:1;border-color:rgb(153 27 27/var(--tw-border-opacity))}.border-slate-200{--tw-border-opacity:1;border-color:rgb(226 232 240/var(--tw-border-opacity))}.border-slate-300{--tw-border-opacity:1;border-color:rgb(203 213 225/var(--tw-border-opacity))}.border-slate-400{--tw-border-opacity:1;border-color:rgb(148 163 184/var(--tw-border-opacity))}.border-slate-50{--tw-border-opacity:1;border-color:rgb(248 250 252/var(--tw-border-opacity))}.border-slate-500{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity))}.border-yellow-200{--tw-border-opacity:1;border-color:rgb(254 240 138/var(--tw-border-opacity))}.bg-cyan-100{--tw-bg-opacity:1;background-color:rgb(207 250 254/var(--tw-bg-opacity))}.bg-cyan-200{--tw-bg-opacity:1;background-color:rgb(165 243 252/var(--tw-bg-opacity))}.bg-cyan-50{--tw-bg-opacity:1;background-color:rgb(236 254 255/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-slate-100{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity))}.bg-slate-50{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity))}.bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-slate-300{--tw-gradient-from:#cbd5e1 var(--tw-gradient-from-position);--tw-gradient-to:rgba(203,213,225,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.fill-cyan-500{fill:#06b6d4}.fill-slate-300{fill:#cbd5e1}.fill-slate-500{fill:#64748b}.fill-slate-800{fill:#1e293b}.stroke-slate-300{stroke:#cbd5e1}.p-12{padding:3rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pl-1{padding-left:.25rem}.pl-12{padding-left:3rem}.pl-2{padding-left:.5rem}.pl-4{padding-left:1rem}.pl-6{padding-left:1.5rem}.pr-2{padding-right:.5rem}.pr-4{padding-right:1rem}.pr-6{padding-right:1.5rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.pt-4{padding-top:1rem}.pt-6{padding-top:1.5rem}.pt-8{padding-top:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.font-mono{font-family:IBM Plex Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-serif{font-family:ui-serif,Georgia,Cambria,Times New Roman,Times,serif}.text-2xl{font-size:1.5rem;line-height:2rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-normal{line-height:1.5}.tracking-normal{letter-spacing:0}.text-cyan-500{--tw-text-opacity:1;color:rgb(6 182 212/var(--tw-text-opacity))}.text-cyan-800{--tw-text-opacity:1;color:rgb(21 94 117/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.text-slate-300{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity))}.text-slate-800{--tw-text-opacity:1;color:rgb(30 41 59/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.underline{text-decoration-line:underline}.decoration-dotted{text-decoration-style:dotted}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}@font-face{font-display:swap;font-family:IBM Plex Sans;font-style:normal;font-weight:400;src:url(../../fonts/ibm-plex-sans-v19-cyrillic_cyrillic-ext_greek_latin_latin-ext_vietnamese-regular.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Sans;font-style:italic;font-weight:400;src:url(../../fonts/ibm-plex-sans-v19-cyrillic_cyrillic-ext_greek_latin_latin-ext_vietnamese-italic.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Sans;font-style:normal;font-weight:700;src:url(../../fonts/ibm-plex-sans-v19-cyrillic_cyrillic-ext_greek_latin_latin-ext_vietnamese-700.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Sans;font-style:italic;font-weight:700;src:url(../../fonts/ibm-plex-sans-v19-cyrillic_cyrillic-ext_greek_latin_latin-ext_vietnamese-700italic.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:normal;font-weight:400;src:url(../../fonts/ibm-plex-mono-v19-cyrillic_cyrillic-ext_latin_latin-ext_vietnamese-regular.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:italic;font-weight:400;src:url(../../fonts/ibm-plex-mono-v19-cyrillic_cyrillic-ext_latin_latin-ext_vietnamese-italic.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:normal;font-weight:700;src:url(../../fonts/ibm-plex-mono-v19-cyrillic_cyrillic-ext_latin_latin-ext_vietnamese-700.woff2) format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:italic;font-weight:700;src:url(../../fonts/ibm-plex-mono-v19-cyrillic_cyrillic-ext_latin_latin-ext_vietnamese-700italic.woff2) format("woff2")}.dropdown{position:relative;display:inline-block}.dropdown-content-right{display:none;position:absolute;z-index:1;margin-left:auto;right:0}.dropdown-content-left{display:none;position:absolute;z-index:1;left:0}.dropdown:hover .dropdown-content-left,.dropdown:hover .dropdown-content-right{display:flex}.triangle-left{position:relative}.triangle-left:before{content:"";border-color:transparent #cbd5e1 transparent transparent;border-style:solid;border-width:9px 8px 9px 0;position:absolute;left:-8px;top:20px}.triangle-left:after{content:"";border-color:transparent #fff transparent transparent;border-style:solid;border-width:9px 8px 9px 0;position:absolute;left:-6px;top:20px}pre{line-height:125%}.syntax-coloring .c{color:#3d7b7b;font-style:italic}.syntax-coloring .err{border:1px solid red}.syntax-coloring .k{color:green;font-weight:700}.syntax-coloring .o{color:#666}.syntax-coloring .ch,.syntax-coloring .cm{color:#3d7b7b;font-style:italic}.syntax-coloring .cp{color:#9c6500}.syntax-coloring .c1,.syntax-coloring .cpf,.syntax-coloring .cs{color:#3d7b7b;font-style:italic}.syntax-coloring .gd{color:#a00000}.syntax-coloring .ge{font-style:italic}.syntax-coloring .ges{font-weight:700;font-style:italic}.syntax-coloring .gr{color:#e40000}.syntax-coloring .gh{color:navy;font-weight:700}.syntax-coloring .gi{color:#008400}.syntax-coloring .go{color:#717171}.syntax-coloring .gp{color:navy;font-weight:700}.syntax-coloring .gs{font-weight:700}.syntax-coloring .gu{color:purple;font-weight:700}.syntax-coloring .gt{color:#04d}.syntax-coloring .kc,.syntax-coloring .kd,.syntax-coloring .kn{color:green;font-weight:700}.syntax-coloring .kp{color:green}.syntax-coloring .kr{color:green;font-weight:700}.syntax-coloring .kt{color:#b00040}.syntax-coloring .m{color:#666}.syntax-coloring .s{color:#ba2121}.syntax-coloring .na{color:#687822}.syntax-coloring .nb{color:green}.syntax-coloring .nc{color:#00f;font-weight:700}.syntax-coloring .no{color:#800}.syntax-coloring .nd{color:#a2f}.syntax-coloring .ni{color:#717171;font-weight:700}.syntax-coloring .ne{color:#cb3f38;font-weight:700}.syntax-coloring .nf{color:#00f}.syntax-coloring .nl{color:#767600}.syntax-coloring .nn{color:#00f;font-weight:700}.syntax-coloring .nt{color:green;font-weight:700}.syntax-coloring .nv{color:#19177c}.syntax-coloring .ow{color:#a2f;font-weight:700}.syntax-coloring .w{color:#bbb}.syntax-coloring .mb,.syntax-coloring .mf,.syntax-coloring .mh,.syntax-coloring .mi,.syntax-coloring .mo{color:#666}.syntax-coloring .dl,.syntax-coloring .sa,.syntax-coloring .sb,.syntax-coloring .sc{color:#ba2121}.syntax-coloring .sd{color:#ba2121;font-style:italic}.syntax-coloring .s2{color:#ba2121}.syntax-coloring .se{color:#aa5d1f;font-weight:700}.syntax-coloring .sh{color:#ba2121}.syntax-coloring .si{color:#a45a77;font-weight:700}.syntax-coloring .sx{color:green}.syntax-coloring .sr{color:#a45a77}.syntax-coloring .s1{color:#ba2121}.syntax-coloring .ss{color:#19177c}.syntax-coloring .bp{color:green}.syntax-coloring .fm{color:#00f}.syntax-coloring .vc,.syntax-coloring .vg,.syntax-coloring .vi,.syntax-coloring .vm{color:#19177c}.syntax-coloring .il{color:#666}input[type=radio]{color:#06b6d4}.hover\:border-b-4:hover{border-bottom-width:4px}.hover\:border-slate-400:hover{--tw-border-opacity:1;border-color:rgb(148 163 184/var(--tw-border-opacity))}.hover\:bg-cyan-400:hover{--tw-bg-opacity:1;background-color:rgb(34 211 238/var(--tw-bg-opacity))}.hover\:bg-slate-100:hover{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.hover\:bg-slate-200:hover{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity))}.hover\:bg-slate-300:hover{--tw-bg-opacity:1;background-color:rgb(203 213 225/var(--tw-bg-opacity))}.focus\:border-cyan-500:focus{--tw-border-opacity:1;border-color:rgb(6 182 212/var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-cyan-200:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(165 243 252/var(--tw-ring-opacity))}.active\:ring:active{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}@media (min-width:768px){.md\:mb-8{margin-bottom:2rem}.md\:h-16{height:4rem}.md\:w-16{width:4rem}.md\:p-4{padding:1rem}.md\:p-8{padding:2rem}.md\:py-4{padding-top:1rem;padding-bottom:1rem}.md\:pb-16{padding-bottom:4rem}.md\:pl-24{padding-left:6rem}.md\:pr-24{padding-right:6rem}.md\:pt-24{padding-top:6rem}}@media (min-width:1024px){.lg\:w-5\/12{width:41.666667%}.lg\:flex-row-reverse{flex-direction:row-reverse}.lg\:pb-0{padding-bottom:0}}@media (min-width:1280px){.xl\:flex{display:flex}} \ No newline at end of file diff --git a/theme/static_src/tailwind.config.js b/theme/static_src/tailwind.config.js index fe43981..5048748 100644 --- a/theme/static_src/tailwind.config.js +++ b/theme/static_src/tailwind.config.js @@ -43,7 +43,7 @@ module.exports = { */ // '../../**/*.py' "../../issues/views.py", - + "../../theme/templatetags/code.py", ], theme: { extend: { diff --git a/theme/templatetags/code.py b/theme/templatetags/code.py new file mode 100644 index 0000000..a6b7140 --- /dev/null +++ b/theme/templatetags/code.py @@ -0,0 +1,40 @@ +from pygments import highlight +from pygments.lexers import get_lexer_by_name +from pygments.formatters import HtmlFormatter + +from django import template + + +register = template.Library() + + +@register.tag(name="code") +def do_code(parser, token): + nodelist = parser.parse(("endcode",)) + parser.delete_first_token() + return CodeNode(nodelist) + + +class CodeNode(template.Node): + + def __init__(self, nodelist): + self.nodelist = nodelist + + def render(self, context): + content = self.nodelist.render(context) + + # remove trailing whitespace from each line (it's never wanted, and in the case of code blocks it can actually + # hurt, e.g. for space-after-backslash in bash) + content = "\n".join([line.rstrip() for line in content.split("\n")]) + + lang_identifier, code = content.split("\n", 1) + assert lang_identifier.startswith(":::") or lang_identifier.startswith("#!"), \ + "Expected code block identifier ':::' or '#!' not " + lang_identifier + + lang = lang_identifier[3:].strip() if lang_identifier.startswith(":::") else lang_identifier[2:].strip() + is_shebang = lang_identifier.startswith("#!") + formatter = HtmlFormatter(linenos="table" if is_shebang else False) + + lexer = get_lexer_by_name(lang, stripall=True) + + return highlight(code, lexer, formatter).replace("highlight", "p-4 mt-4 bg-slate-50 syntax-coloring")