diff --git a/.gitignore b/.gitignore index 25db0487..a4d3ddee 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,11 @@ __pycache__/ *.py[cod] *$py.class *.swp +www/privacy.html* +www/index.html* +www/info.html* +*qr-*.png + # C extensions *.so diff --git a/README.md b/README.md index f88a891a..f7418fb6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ + + + # Chatmail instances optimized for Delta Chat apps This repository helps to setup a ready-to-use chatmail instance @@ -22,27 +25,30 @@ after which the initially specified password is required for using them. export CHATMAIL_DOMAIN=c1.testrun.org # replace with your host -4. Deploy the chat mail instance to your chatmail server: +4. Fill in privacy contact data into the `chatmail.ini` file + +5. Deploy the chat mail instance to your chatmail server: scripts/deploy.sh - This script uses `pyinfra` and `ssh` to setup packages and configure - the chatmail instance on your remote server. + This script remotely sets up packages and configures the chatmail provider. -5. Run `scripts/generate-dns-zone.sh` and +6. Run `scripts/generate-dns-zone.sh` and transfer the generated DNS records at your DNS provider ### Home page and getting started for users -- The `deploy.sh` script deploys a default `index.html` - along with a QR code that users can click to - create accounts on the chatmail provider. +The `deploy.sh` script deploys -- Start a Delta Chat app and create a new account - by typing an e-mail address with an arbitrary username - and `@` appended. - Use an at least 10-character random password. +- a default `index.html` along with a QR code that users can click to + create accounts on your chatmail provider, + +- a default `info.html` that is linked from the home page, + +- a default `policy.html` that is linked from the home page. + +All files are generated by the according markdown `.md` file in the `www` directory. ### Ports diff --git a/chatmail.ini b/chatmail.ini new file mode 100644 index 00000000..2d9dee5f --- /dev/null +++ b/chatmail.ini @@ -0,0 +1,15 @@ +[config] + +privacy_postal = + Merlinux GmbH, Represented by the managing director H. Krekel, + Reichgrafen Str. 20, 79102 Freiburg, Germany + +privacy_mail = delta-privacy@merlinux.eu + +privacy_pdo = + Prof. Dr. Fabian Schmieder, lexICT UG (limited), Ostfeldstr. 49, 30559 Hannover. + You can contact him at *delta-privacy@merlinux.eu* (Keyword: DPO) + +privacy_supervisor = + State Commissioner for Data Protection and Freedom of Information of + Baden-Württemberg in 70173 Stuttgart, Germany. diff --git a/deploy-chatmail/pyproject.toml b/deploy-chatmail/pyproject.toml index 27ef46d0..9073d81e 100644 --- a/deploy-chatmail/pyproject.toml +++ b/deploy-chatmail/pyproject.toml @@ -7,7 +7,9 @@ name = "deploy-chatmail" version = "0.1" dependencies = [ "pyinfra", + "pillow", "qrcode", + "markdown", ] [tool.pytest.ini_options] diff --git a/deploy-chatmail/src/deploy_chatmail/__init__.py b/deploy-chatmail/src/deploy_chatmail/__init__.py index 77e97d10..197526bf 100644 --- a/deploy-chatmail/src/deploy_chatmail/__init__.py +++ b/deploy-chatmail/src/deploy_chatmail/__init__.py @@ -2,6 +2,8 @@ Chat Mail pyinfra deploy. """ import importlib.resources +import configparser +import textwrap from pathlib import Path from pyinfra import host @@ -9,6 +11,9 @@ from pyinfra.operations import apt, files, server, systemd from pyinfra.facts.files import File from pyinfra.facts.systemd import SystemdEnabled from .acmetool import deploy_acmetool +import markdown +from jinja2 import Template + from .genqr import gen_qr_png_data @@ -302,18 +307,83 @@ def _configure_nginx(domain: str, debug: bool = False) -> bool: mode="755", ) - qr_data = gen_qr_png_data(domain) + return need_restart - files.put( - name="Upload QR code for account creation", - src=qr_data, - dest="/var/www/html/qrcode.png", - user="root", - group="root", - mode="644", + +def get_ini_settings(mail_domain, inipath): + parser = configparser.ConfigParser() + parser.read(inipath) + settings = {key: value.strip() for (key, value) in parser["config"].items()} + if mail_domain != "testrun.org" and not mail_domain.endswith(".testrun.org"): + for value in settings.values(): + value = value.lower() + if "merlinux" in value or "schmieder" in value or "@testrun.org" in value: + raise ValueError( + f"please set your own privacy contacts/addresses in {inipath}" + ) + settings["mail_domain"] = mail_domain + return settings + + +def build_htmlj2_from_markdown(source): + assert source.exists(), source + template_content = open(source).read() + if source.stem == "privacy": + title = "privacy {{ config.mail_domain }}" + elif source.stem == "index": + title = "home {{ config.mail_domain }}" + elif source.stem == "info": + title = "info {{ config.mail_domain }}" + + html = markdown.markdown(template_content) + html = ( + textwrap.dedent( + f"""\ + + + + + {title} + + + + """ + ) + + html + + "\n" + + textwrap.dedent( + """\ + + """ + ) ) - return need_restart + target_path = source.with_name(source.stem + ".html.j2") + with open(target_path, "w") as f: + f.write(html) + print(f"wrote {target_path}") + return target_path + + +def build_webpages(www_path, config): + mail_domain = config["mail_domain"] + qr_data = gen_qr_png_data(mail_domain).read() + www_path.joinpath(f"qr-chatmail-invite-{mail_domain}.png").write_bytes(qr_data) + + for path in www_path.iterdir(): + if path.suffix == ".md": + path = build_htmlj2_from_markdown(path) + + if path.suffix == ".j2": + target = path.with_name(path.name[:-3]) + template = Template(path.read_text()) + with target.open("w") as f: + f.write(template.render(config=config)) def deploy_chatmail(mail_domain: str, mail_server: str, dkim_selector: str) -> None: @@ -367,6 +437,14 @@ def deploy_chatmail(mail_domain: str, mail_server: str, dkim_selector: str) -> N packages=["fcgiwrap"], ) + pkg_root = importlib.resources.files(__package__) + chatmail_ini = pkg_root.joinpath("../../../chatmail.ini").resolve() + config = get_ini_settings(mail_domain, chatmail_ini) + www_path = pkg_root.joinpath("../../../www").resolve() + + build_webpages(www_path, config) + files.rsync(f"{www_path}/", "/var/www/html", flags=["-avz"]) + _install_chatmaild() debug = False dovecot_need_restart = _configure_dovecot(mail_server, debug=debug) @@ -375,22 +453,6 @@ def deploy_chatmail(mail_domain: str, mail_server: str, dkim_selector: str) -> N mta_sts_need_restart = _install_mta_sts_daemon() nginx_need_restart = _configure_nginx(mail_domain) - # deploy web pages and info if we have them - pkg_root = importlib.resources.files(__package__) - www_path = pkg_root.joinpath(f"../../../www/{mail_domain}").resolve() - if www_path.is_dir(): - files.rsync(f"{www_path}/", "/var/www/html", flags=["-avz"]) - else: - index_path = www_path.parent.joinpath("default/index.html.j2") - files.template( - src=index_path, - dest="/var/www/html/index.html", - user="root", - group="root", - mode="644", - config={"mail_domain": mail_domain}, - ) - systemd.service( name="Start and enable OpenDKIM", service="opendkim.service", diff --git a/deploy-chatmail/src/deploy_chatmail/genqr.py b/deploy-chatmail/src/deploy_chatmail/genqr.py index fa47d28c..8ae7fd24 100644 --- a/deploy-chatmail/src/deploy_chatmail/genqr.py +++ b/deploy-chatmail/src/deploy_chatmail/genqr.py @@ -18,7 +18,8 @@ def gen_qr(maildomain, url): # taken and modified from # https://github.com/deltachat/mailadm/blob/master/src/mailadm/gen_qr.py - info = f"{maildomain} invite code" + # info = f"{maildomain} invite code" + info = "" # load QR code qr = qrcode.QRCode( @@ -43,7 +44,7 @@ def gen_qr(maildomain, url): font_size = 16 font = ImageFont.truetype(font=ttf_path, size=font_size) - num_lines = (info).count("\n") + 1 + num_lines = ((info).count("\n") + 1) if info else 0 size = width = 384 qr_padding = 6 @@ -51,20 +52,24 @@ def gen_qr(maildomain, url): height = size + text_height + qr_padding * 2 image = Image.new("RGBA", (width, height), "white") - - draw = ImageDraw.Draw(image) - qr_final_size = width - (qr_padding * 2) - # draw text - if hasattr(font, "getsize"): - info_pos = (width - font.getsize(info.strip())[0]) // 2 - else: - info_pos = (width - font.getbbox(info.strip())[3]) // 2 + if num_lines: + draw = ImageDraw.Draw(image) - draw.multiline_text( - (info_pos, size - qr_padding // 2), info, font=font, fill="black", align="right" - ) + # draw text + if hasattr(font, "getsize"): + info_pos = (width - font.getsize(info.strip())[0]) // 2 + else: + info_pos = (width - font.getbbox(info.strip())[3]) // 2 + + draw.multiline_text( + (info_pos, size - qr_padding // 2), + info, + font=font, + fill="black", + align="right", + ) # paste QR code image.paste( diff --git a/tests/test_helpers.py b/tests/test_helpers.py new file mode 100644 index 00000000..683ed435 --- /dev/null +++ b/tests/test_helpers.py @@ -0,0 +1,39 @@ +import textwrap + +from deploy_chatmail import build_htmlj2_from_markdown, get_ini_settings + + +def test_markdown(tmp_path): + path = tmp_path.joinpath("privacy.md") + path.write_text("# privacy policy") + build_htmlj2_from_markdown(path) + output = path.with_name("privacy.html.j2") + assert output.exists() + print(output.read_text()) + + +def test_get_settings(tmp_path): + inipath = tmp_path.joinpath("chatmail.ini") + inipath.write_text( + textwrap.dedent( + """\ + [config] + + privacy_postal = + address-line1 + address-line2 + + privacy_mail = privacy@example.org + + privacy_pdo = + address-line3 + """ + ) + ) + d = get_ini_settings("x.testrun.org", inipath) + assert d["privacy_postal"] == "address-line1\naddress-line2" + assert d["privacy_mail"] == "privacy@example.org" + assert d["privacy_pdo"] == "address-line3" + assert d["mail_domain"] == "x.testrun.org" + + diff --git a/www/collage-info.png b/www/collage-info.png new file mode 100644 index 00000000..fe4abe34 Binary files /dev/null and b/www/collage-info.png differ diff --git a/www/collage-privacy.png b/www/collage-privacy.png new file mode 100644 index 00000000..d8ae7e78 Binary files /dev/null and b/www/collage-privacy.png differ diff --git a/www/collage-top.png b/www/collage-top.png new file mode 100644 index 00000000..cca96f89 Binary files /dev/null and b/www/collage-top.png differ diff --git a/www/default/index.html.j2 b/www/default/index.html.j2 deleted file mode 100644 index 72cc7ebd..00000000 --- a/www/default/index.html.j2 +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - chatmail instance - - -

Welcome to {{ config.mail_domain }}!

-

Getting started

-
    -
  1. Install https://get.delta.chat
  2. -
  3. Scan or Tap on the invite QR code
  4. -
  5. Choose Nickname and Avatar
  6. -
  7. Setup contact with others using - guaranteed end-to-end encryption via QR code scans -
  8. -
- - - - -

Constraints

- - - diff --git a/www/index.md b/www/index.md new file mode 100644 index 00000000..46a83a1e --- /dev/null +++ b/www/index.md @@ -0,0 +1,19 @@ + + + +## Dear [Delta Chat](https://get.delta.chat) users and newcomers, + +Welcome to instant, interoperable and [privacy-preserving](privacy.html) messaging :) + +👉 **Tap** or scan this QR code to get a random `@{{config.mail_domain}}` e-mail address + + + + +🐣 **Choose** your Avatar and Name + +💬 **Start** chatting with any Delta Chat contacts using [QR invite codes](https://delta.chat/en/help#howtoe2ee) + + +## ⚡ Note: this is an experimental service ⚡ + diff --git a/www/info.md b/www/info.md new file mode 100644 index 00000000..fb29ca14 --- /dev/null +++ b/www/info.md @@ -0,0 +1,39 @@ + + + +## More information + +### Choosing a chatmail address instead of using a random one + +In the Delta Chat account setup +you may tap `LOG INTO YOUR E-MAIL ACCOUNT` +and fill the two fields like this: + +- `Address`: invent a word with *exactly* nine characters + and append `@{{config.mail_domain}}` to it. + +- `Password`: invent at least 10 characters. + +If the e-mail address is not yet taken, you'll get that account. +The first login sets your password. + + +### Rate and storage limits + +- Un-encrypted messages are blocked to recipients outside + {{config.mail_domain}} but setting up contact via [QR invite codes](https://delta.chat/en/help#howtoe2ee) + allows your messages to pass freely to any outside recipients. + +- You may send up to 60 messages per minute + +- Messages are unconditionally removed 40 days after arriving on the server + +- You can store up to [100MB messages on the server](https://delta.chat/en/help#what-happens-if-i-turn-on-delete-old-messages-from-server) + + +### Who are the operators? Which software is running? + +This chatmail provider is run by a small voluntary group of devs and sysadmins, +who [publically develop chatmail provider setups](https://github.com/deltachat/chatmail). +Chatmail setups aim to be very low-maintenance, resource efficient and +interoperable with any other standards-compliant e-mail service. diff --git a/www/nine.testrun.org/collage-bg.png b/www/nine.testrun.org/collage-bg.png deleted file mode 100644 index 2b33e00c..00000000 Binary files a/www/nine.testrun.org/collage-bg.png and /dev/null differ diff --git a/www/nine.testrun.org/collage-down.png b/www/nine.testrun.org/collage-down.png deleted file mode 100644 index dc1c384d..00000000 Binary files a/www/nine.testrun.org/collage-down.png and /dev/null differ diff --git a/www/nine.testrun.org/collage-top.png b/www/nine.testrun.org/collage-top.png deleted file mode 100644 index 698ec0b5..00000000 Binary files a/www/nine.testrun.org/collage-top.png and /dev/null differ diff --git a/www/nine.testrun.org/index.html b/www/nine.testrun.org/index.html deleted file mode 100644 index dbecf1bc..00000000 --- a/www/nine.testrun.org/index.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - nine.testrun.org - Experimenting with the Future of Email - - - - -
- -
-

Dear Delta Chat users and newcomers,

-

- welcome to the first public "chat-mail instance", - a small and lean e-mail provider for smooth chatting. - Install Delta Chat and then - Tap or scan this QR code to obtain a random e-mail address: - - -

-

- Alternatively, you can manually invent an e-mail address: -

    -
  • Tap "LOG INTO YOUR E-MAIL ACCOUNT".
  • -
  • Address: invent a word with exactly nine characters - and append @nine.testrun.org to it.
  • -
  • Password: invent at least 10 characters. The first login sets your password.
  • -
- If the e-mail address is not yet taken, you'll get that account. -

-

- - -

What's behind it, how does it operate?

-

nine.testrun.org is run - by a small group of devs and sysadmins, reachable via root@. - They want to keep this instance running at least until end 2024. - Current limits: -

    -
  • Un-encrypted mails can not leave the chat-mail instance.
  • -
  • Use - guaranteed end-to-end encryption via QR code scans - to setup contact with users outside of the chat-mail instance. -
  • -
  • You may send up to 60 messages per minute.
  • -
  • Messages are unconditionally removed 40 days after arrival.
  • -
  • Max storage per user is 100MB.
  • -
-

Why are other email providers 1000 times more complicated?

-

¯\_(ツ)_/¯

-
-
- - diff --git a/www/privacy.md b/www/privacy.md new file mode 100644 index 00000000..c35e0be1 --- /dev/null +++ b/www/privacy.md @@ -0,0 +1,321 @@ + + +# Privacy Policy for {{ config.mail_domain }} + +We want to show you in a fair and transparent way +what personal data is processed by us. +We follow a strict privacy-by-design approach +and try to avoid processing your data in the first place, +but as you may know, +the internet, +and in particular sending e-mail messages, +does not work without data. +Still, +it's only fair that you know at all times +what personal data is processed +when you use our service. + +If you have any remaining questions about data protection, please contact us. + +## 1. Name and contact information + +Responsible for the processing of your personal data is: +``` +{{ config.privacy_postal }} +``` + +E-mail: {{ config.privacy_mail }} + +We have appointed a data protection officer: + +``` +{{ config.privacy_pdo }} +``` + +## 2. Processing when using chat e-mail services + +We provide e-mail services optimized for the use from [Delta Chat](https://delta.chat) apps +and process only the data necessary +for the setup and technical execution of the e-mail dispatch. +The purpose of the processing is to +read, write, manage, delete, send, and receive emails. +For this purpose, +we operate server-side software +that enables us to send and receive e-mail messages. +Allowing the use of the e-mail service, +we process the following data and details: + +- Outgoing and incoming messages (SMTP) are stored for transit + on behalf of their users until the message can be delivered. + +- E-Mail-Messages are stored for the recipient and made accessible via IMAP protocols, + until explicitly deleted by the user or until a fixed time period is exceeded, + (*usually 4-8 weeks*). + +- IMAP and SMTP protocols are password protected with unique credentials for each account. + +- Users can retrieve or delete all stored messages + without intervention from the operators using standard IMAP client tools. + +### 3.1 Account setup + +Creating an account happens in one of two ways on our mail servers: + +- with a QR invitation token + which is scanned using the DeltaChat app + and then the account is created. + +- by letting Delta Chat otherwise create an account + and register it with a {{ config.mail_domain }} mail server. + +In either case, we process the newly created email address. +No phone numbers, +other email addresses, +or other identifiable data +is currently required. +The legal basis for the processing is +Art. 6 (1) lit. b GDPR, +as you have a usage contract with us +by using our services. + +## 3.2 Processing of E-Mail-Messages + +In addition, +we will process data +to keep the server infrastructure operational +for purposes of e-mail dispatch +and abuse prevention. + +- Therefore, + it is necessary to process the content and/or metadata + (e.g., headers of the email as well as smtp chatter) + of E-Mail-Messages in transit. + +- We will keep logs of messages in transit for a limited time. + These logs are used to debug delivery problems and software bugs. + +In addition, +we process data to protect the systems from excessive use. +Therefore, limits are enforced: + +- rate limits + +- storage limits + +- message size limits + +- any other limit neccessary for the whole server to function in a healthy way + and to prevent abuse. + +The processing and use of the above permissions +are performed to provide the service. +The data processing is necessary for the use of our services, +therefore the legal basis of the processing is +Art. 6 (1) lit. b GDPR, +as you have a usage contract with us +by using our services. +The legal basis for the data processing +for the purposes of security and abuse prevention is +Art. 6 (1) lit. f GDPR. +Our legitimate interest results +from the aforementioned purposes. +We will not use the collected data +for the purpose of drawing conclusions +about your person. + + +## 3. Processing when using our Website + +When you visit our website, +the browser used on your end device +automatically sends information to the server of our website. +This information is temporarily stored in a so-called log file. +The following information is collected and stored +until it is automatically deleted +(*usually 7 days*): + +- used type of browser, + +- used operating system, + +- access date and time as well as + +- country of origin and IP address, + +- the requested file name or HTTP resource, + +- the amount of data transferred, + +- the access status (file transferred, file not found, etc.) and + +- the page from which the file was requested. + +This website is hosted by an external service provider (hoster). +The personal data collected on this website is stored +on the hoster's servers. +Our hoster will process your data +only to the extent necessary to fulfill its obligations +to perform under our instructions. +In order to ensure data protection-compliant processing, +we have concluded a data processing agreement with our hoster. + +The aforementioned data is processed by us for the following purposes: + +- Ensuring a reliable connection setup of the website, + +- ensuring a convenient use of our website, + +- checking and ensuring system security and stability, and + +- for other administrative purposes. + +The legal basis for the data processing is +Art. 6 (1) lit. f GDPR. +Our legitimate interest results +from the aforementioned purposes of data collection. +We will not use the collected data +for the purpose of drawing conclusions about your person. + +## 4. Transfer of Data + +Your personal data +will not be transferred to third parties +for purposes other than those listed below: + +a) you have given your express consent +in accordance with Art. 6 para. 1 sentence 1 lit. a GDPR, + +b) the disclosure is necessary for the assertion, exercise or defence of legal claims +pursuant to Art. 6 (1) sentence 1 lit. f GDPR +and there is no reason to assume that you have +an overriding interest worthy of protection +in the non-disclosure of your data, + +c) in the event that there is a legal obligation to disclose your data +pursuant to Art. 6 para. 1 sentence 1 lit. c GDPR, +as well as + +d) this is legally permissible and necessary +in accordance with Art. 6 Para. 1 S. 1 lit. b GDPR +for the processing of contractual relationships with you, + +e) this is carried out by a service provider +acting on our behalf and on our exclusive instructions, +whom we have carefully selected (Art. 28 (1) GDPR) +and with whom we have concluded a corresponding contract on commissioned processing (Art. 28 (3) GDPR), +which obliges our contractor, +among other things, +to implement appropriate security measures +and grants us comprehensive control powers. + +## 5. Rights of the data subject + +The rights arise from Articles 12 to 23 GDPR. +Since no personal data is stored on our servers, +even in encrypted form, +there is no need to provide information +on these or possible objections. +A deletion can be made +directly in the Delta Chat email messenger. + +a) request information about your personal data processed by us +in accordance with Art. 15 GDPR. +In particular, +you can request information about the processing purposes, +the category of personal data, +the categories of recipients to whom your data have been or will be disclosed, +the planned storage period, +the existence of a right to rectification, erasure, restriction of processing or objection, +the existence of a right of complaint, +the origin of your data if it has not been collected by us, +as well as the existence of automated decision-making including profiling +and, if applicable, +meaningful information about its details; + +b) in accordance with Art. 16 of the GDPR, +immediately request the correction +of inaccurate or incomplete personal data stored by us; + +c) pursuant to Article 17 of the GDPR, +to request the erasure of your personal data stored by us, +unless the processing is necessary +for the exercise of the right to freedom of expression and information, +for compliance with a legal obligation, +for reasons of public interest, +or for the establishment, exercise or defence of legal claims; + +d) pursuant to Art. 18 GDPR, +to request the restriction of the processing of your personal data, +insofar as the accuracy of the data is disputed by you, +the processing is unlawful, +but you object to its erasure +and we no longer require the data, +but you need it for the assertion, exercise or defence of legal claims +or you have objected to the processing pursuant to Art. 21 GDPR; + +e) pursuant to Art. 20 GDPR, +to receive your personal data that you have provided to us +in a structured, common and machine-readable format +or to request that it be transferred to another controller; + +f) in accordance with Art. 7 (3) of the GDPR, +to revoke your consent given to us at any time. +This has the consequence that we may no longer continue the data processing +based on this consent in the future; and + +g) complain to a supervisory authority +in accordance with Article 77 of the GDPR. +As a rule, +you can contact the supervisory authority of your usual place of residence +or workplace +or our registered office for this purpose. +The supervisory authority responsible for our place of business +is the `{{ config.privacy_supervisor }}`. + +If you have any questions or complaints, please feel free to contact us by email: +{{ config.privacy_mail }} + + +### 5.1 Right to object + +If your personal data is processed on the basis of our legitimate interests +in accordance with Art. 6 (1) lit. f GDPR, +you have the right to object to the processing of your personal data +in accordance with Art. 21 GDPR, +provided that there are grounds for this based on your particular situation +or the objection is directed against direct advertising. +In the latter case, +you have a general right of objection, +which will be implemented by us +without specifying a particular situation. + +If you wish to exercise your right of objection, +simply send an e-mail to: {{ config.privacy_mail }} + +### 5.2 Right to withdraw + +If your personal data is processed on the basis of your consent +in accordance with Art. 6 (1) lit. a GDPR +(e.g. via the mailing list), +you can withdraw your consent at any time +and without any disadvantages. +As a result, +we may no longer continue the data processing +that was based on this consent for the future. +However, +the withdrawal of your consent +does not affect the lawfulness of the processing +carried out on the basis of the consent until the withdrawal. + +If you wish to make use of your right of withdrawal, +simply send an e-mail to: {{ config.privacy_mail }} + +## 6. Validity of this privacy policy + +This data protection declaration is valid +as of *December 2023*. +Due to the further development of our service and offers +or due to changed legal or official requirements, +it may become necessary to revise this data protection declaration from time to time. + + diff --git a/www/water.css b/www/water.css new file mode 100644 index 00000000..74a5a164 --- /dev/null +++ b/www/water.css @@ -0,0 +1,1690 @@ +/** + * Automatic version: + * Uses light theme by default but switches to dark theme + * if a system-wide theme preference is set on the user's device. + */ + +:root { + --background-body: #fff; + --background: #efefef; + --background-alt: #f7f7f7; + --selection: #9e9e9e; + --text-main: #363636; + --text-bright: #000; + --text-muted: #70777f; + --links: #0076d1; + --focus: #0096bfab; + --border: #dbdbdb; + --code: #000; + --animation-duration: 0.1s; + --button-base: #d0cfcf; + --button-hover: #9b9b9b; + --scrollbar-thumb: rgb(170, 170, 170); + --scrollbar-thumb-hover: var(--button-hover); + --form-placeholder: #949494; + --form-text: #1d1d1d; + --variable: #39a33c; + --highlight: #ff0; + --select-arrow: url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E"); +} + +@media (prefers-color-scheme: dark) { +:root { + --background-body: #202b38; + --background: #161f27; + --background-alt: #1a242f; + --selection: #1c76c5; + --text-main: #dbdbdb; + --text-bright: #fff; + --text-muted: #a9b1ba; + --links: #41adff; + --focus: #0096bfab; + --border: #526980; + --code: #ffbe85; + --animation-duration: 0.1s; + --button-base: #0c151c; + --button-hover: #040a0f; + --scrollbar-thumb: var(--button-hover); + --scrollbar-thumb-hover: rgb(0, 0, 0); + --form-placeholder: #a9a9a9; + --form-text: #fff; + --variable: #d941e2; + --highlight: #efdb43; + --select-arrow: url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E"); +} +} + +html { + scrollbar-color: rgb(170, 170, 170) #fff; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + scrollbar-width: thin; +} + +@media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } +} + +@media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } +} + +@media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } +} + +@media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } +} + +@media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } +} + +@media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } +} + +body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif; + line-height: 1.4; + font-size: 1.4em; + max-width: 800px; + margin: 20px auto; + padding: 0 10px; + word-wrap: break-word; + color: #363636; + color: var(--text-main); + background: #fff; + background: var(--background-body); + text-rendering: optimizeLegibility; +} + +@media (prefers-color-scheme: dark) { + + body { + background: #202b38; + background: var(--background-body); + } +} + +@media (prefers-color-scheme: dark) { + + body { + color: #dbdbdb; + color: var(--text-main); + } +} + +button { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; +} + +@media (prefers-color-scheme: dark) { + + button { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; + } +} + +input { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; +} + +@media (prefers-color-scheme: dark) { + + input { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; + } +} + +textarea { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; +} + +@media (prefers-color-scheme: dark) { + + textarea { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; + } +} + +h1 { + font-size: 2.2em; + margin-top: 0; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin-bottom: 12px; + margin-top: 24px; +} + +h1 { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + h1 { + color: #fff; + color: var(--text-bright); + } +} + +h2 { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + h2 { + color: #fff; + color: var(--text-bright); + } +} + +h3 { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + h3 { + color: #fff; + color: var(--text-bright); + } +} + +h4 { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + h4 { + color: #fff; + color: var(--text-bright); + } +} + +h5 { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + h5 { + color: #fff; + color: var(--text-bright); + } +} + +h6 { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + h6 { + color: #fff; + color: var(--text-bright); + } +} + +strong { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + strong { + color: #fff; + color: var(--text-bright); + } +} + +h1, +h2, +h3, +h4, +h5, +h6, +b, +strong, +th { + font-weight: 600; +} + +q::before { + content: none; +} + +q::after { + content: none; +} + +blockquote { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + margin: 1.5em 0; + padding: 0.5em 1em; + font-style: italic; +} + +@media (prefers-color-scheme: dark) { + + blockquote { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + } +} + +q { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + margin: 1.5em 0; + padding: 0.5em 1em; + font-style: italic; +} + +@media (prefers-color-scheme: dark) { + + q { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + } +} + +blockquote > footer { + font-style: normal; + border: 0; +} + +blockquote cite { + font-style: normal; +} + +address { + font-style: normal; +} + +a[href^='mailto\:']::before { + content: '📧 '; +} + +a[href^='tel\:']::before { + content: '📞 '; +} + +a[href^='sms\:']::before { + content: '💬 '; +} + +mark { + background-color: #ff0; + background-color: var(--highlight); + border-radius: 2px; + padding: 0 2px 0 2px; + color: #000; +} + +@media (prefers-color-scheme: dark) { + + mark { + background-color: #efdb43; + background-color: var(--highlight); + } +} + +a > code, +a > strong { + color: inherit; +} + +button, +select, +input[type='submit'], +input[type='reset'], +input[type='button'], +input[type='checkbox'], +input[type='range'], +input[type='radio'] { + cursor: pointer; +} + +input, +select { + display: block; +} + +[type='checkbox'], +[type='radio'] { + display: initial; +} + +input { + color: #1d1d1d; + color: var(--form-text); + background-color: #efefef; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; +} + +@media (prefers-color-scheme: dark) { + + input { + background-color: #161f27; + background-color: var(--background); + } +} + +@media (prefers-color-scheme: dark) { + + input { + color: #fff; + color: var(--form-text); + } +} + +button { + color: #1d1d1d; + color: var(--form-text); + background-color: #efefef; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; +} + +@media (prefers-color-scheme: dark) { + + button { + background-color: #161f27; + background-color: var(--background); + } +} + +@media (prefers-color-scheme: dark) { + + button { + color: #fff; + color: var(--form-text); + } +} + +textarea { + color: #1d1d1d; + color: var(--form-text); + background-color: #efefef; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; +} + +@media (prefers-color-scheme: dark) { + + textarea { + background-color: #161f27; + background-color: var(--background); + } +} + +@media (prefers-color-scheme: dark) { + + textarea { + color: #fff; + color: var(--form-text); + } +} + +select { + color: #1d1d1d; + color: var(--form-text); + background-color: #efefef; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; +} + +@media (prefers-color-scheme: dark) { + + select { + background-color: #161f27; + background-color: var(--background); + } +} + +@media (prefers-color-scheme: dark) { + + select { + color: #fff; + color: var(--form-text); + } +} + +button { + background-color: #d0cfcf; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; +} + +@media (prefers-color-scheme: dark) { + + button { + background-color: #0c151c; + background-color: var(--button-base); + } +} + +input[type='submit'] { + background-color: #d0cfcf; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; +} + +@media (prefers-color-scheme: dark) { + + input[type='submit'] { + background-color: #0c151c; + background-color: var(--button-base); + } +} + +input[type='reset'] { + background-color: #d0cfcf; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; +} + +@media (prefers-color-scheme: dark) { + + input[type='reset'] { + background-color: #0c151c; + background-color: var(--button-base); + } +} + +input[type='button'] { + background-color: #d0cfcf; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; +} + +@media (prefers-color-scheme: dark) { + + input[type='button'] { + background-color: #0c151c; + background-color: var(--button-base); + } +} + +button:hover { + background: #9b9b9b; + background: var(--button-hover); +} + +@media (prefers-color-scheme: dark) { + + button:hover { + background: #040a0f; + background: var(--button-hover); + } +} + +input[type='submit']:hover { + background: #9b9b9b; + background: var(--button-hover); +} + +@media (prefers-color-scheme: dark) { + + input[type='submit']:hover { + background: #040a0f; + background: var(--button-hover); + } +} + +input[type='reset']:hover { + background: #9b9b9b; + background: var(--button-hover); +} + +@media (prefers-color-scheme: dark) { + + input[type='reset']:hover { + background: #040a0f; + background: var(--button-hover); + } +} + +input[type='button']:hover { + background: #9b9b9b; + background: var(--button-hover); +} + +@media (prefers-color-scheme: dark) { + + input[type='button']:hover { + background: #040a0f; + background: var(--button-hover); + } +} + +input[type='color'] { + min-height: 2rem; + padding: 8px; + cursor: pointer; +} + +input[type='checkbox'], +input[type='radio'] { + height: 1em; + width: 1em; +} + +input[type='radio'] { + border-radius: 100%; +} + +input { + vertical-align: top; +} + +label { + vertical-align: middle; + margin-bottom: 4px; + display: inline-block; +} + +input:not([type='checkbox']):not([type='radio']), +input[type='range'], +select, +button, +textarea { + -webkit-appearance: none; +} + +textarea { + display: block; + margin-right: 0; + box-sizing: border-box; + resize: vertical; +} + +textarea:not([cols]) { + width: 100%; +} + +textarea:not([rows]) { + min-height: 40px; + height: 140px; +} + +select { + background: #efefef url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + padding-right: 35px; +} + +@media (prefers-color-scheme: dark) { + + select { + background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + } +} + +@media (prefers-color-scheme: dark) { + + select { + background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + } +} + +@media (prefers-color-scheme: dark) { + + select { + background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + } +} + +@media (prefers-color-scheme: dark) { + + select { + background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + } +} + +select::-ms-expand { + display: none; +} + +select[multiple] { + padding-right: 10px; + background-image: none; + overflow-y: auto; +} + +input:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); +} + +@media (prefers-color-scheme: dark) { + + input:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } +} + +select:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); +} + +@media (prefers-color-scheme: dark) { + + select:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } +} + +button:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); +} + +@media (prefers-color-scheme: dark) { + + button:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } +} + +textarea:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); +} + +@media (prefers-color-scheme: dark) { + + textarea:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } +} + +input[type='checkbox']:active, +input[type='radio']:active, +input[type='submit']:active, +input[type='reset']:active, +input[type='button']:active, +input[type='range']:active, +button:active { + transform: translateY(2px); +} + +input:disabled, +select:disabled, +button:disabled, +textarea:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +::-moz-placeholder { + color: #949494; + color: var(--form-placeholder); +} + +:-ms-input-placeholder { + color: #949494; + color: var(--form-placeholder); +} + +::-ms-input-placeholder { + color: #949494; + color: var(--form-placeholder); +} + +::placeholder { + color: #949494; + color: var(--form-placeholder); +} + +@media (prefers-color-scheme: dark) { + + ::-moz-placeholder { + color: #a9a9a9; + color: var(--form-placeholder); + } + + :-ms-input-placeholder { + color: #a9a9a9; + color: var(--form-placeholder); + } + + ::-ms-input-placeholder { + color: #a9a9a9; + color: var(--form-placeholder); + } + + ::placeholder { + color: #a9a9a9; + color: var(--form-placeholder); + } +} + +fieldset { + border: 1px #0096bfab solid; + border: 1px var(--focus) solid; + border-radius: 6px; + margin: 0; + margin-bottom: 12px; + padding: 10px; +} + +@media (prefers-color-scheme: dark) { + + fieldset { + border: 1px #0096bfab solid; + border: 1px var(--focus) solid; + } +} + +legend { + font-size: 0.9em; + font-weight: 600; +} + +input[type='range'] { + margin: 10px 0; + padding: 10px 0; + background: transparent; +} + +input[type='range']:focus { + outline: none; +} + +input[type='range']::-webkit-slider-runnable-track { + width: 100%; + height: 9.5px; + -webkit-transition: 0.2s; + transition: 0.2s; + background: #efefef; + background: var(--background); + border-radius: 3px; +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-webkit-slider-runnable-track { + background: #161f27; + background: var(--background); + } +} + +input[type='range']::-webkit-slider-thumb { + box-shadow: 0 1px 1px #000, 0 0 1px #0d0d0d; + height: 20px; + width: 20px; + border-radius: 50%; + background: #dbdbdb; + background: var(--border); + -webkit-appearance: none; + margin-top: -7px; +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-webkit-slider-thumb { + background: #526980; + background: var(--border); + } +} + +input[type='range']:focus::-webkit-slider-runnable-track { + background: #efefef; + background: var(--background); +} + +@media (prefers-color-scheme: dark) { + + input[type='range']:focus::-webkit-slider-runnable-track { + background: #161f27; + background: var(--background); + } +} + +input[type='range']::-moz-range-track { + width: 100%; + height: 9.5px; + -moz-transition: 0.2s; + transition: 0.2s; + background: #efefef; + background: var(--background); + border-radius: 3px; +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-moz-range-track { + background: #161f27; + background: var(--background); + } +} + +input[type='range']::-moz-range-thumb { + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; + height: 20px; + width: 20px; + border-radius: 50%; + background: #dbdbdb; + background: var(--border); +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-moz-range-thumb { + background: #526980; + background: var(--border); + } +} + +input[type='range']::-ms-track { + width: 100%; + height: 9.5px; + background: transparent; + border-color: transparent; + border-width: 16px 0; + color: transparent; +} + +input[type='range']::-ms-fill-lower { + background: #efefef; + background: var(--background); + border: 0.2px solid #010101; + border-radius: 3px; + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-ms-fill-lower { + background: #161f27; + background: var(--background); + } +} + +input[type='range']::-ms-fill-upper { + background: #efefef; + background: var(--background); + border: 0.2px solid #010101; + border-radius: 3px; + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-ms-fill-upper { + background: #161f27; + background: var(--background); + } +} + +input[type='range']::-ms-thumb { + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; + border: 1px solid #000; + height: 20px; + width: 20px; + border-radius: 50%; + background: #dbdbdb; + background: var(--border); +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-ms-thumb { + background: #526980; + background: var(--border); + } +} + +input[type='range']:focus::-ms-fill-lower { + background: #efefef; + background: var(--background); +} + +@media (prefers-color-scheme: dark) { + + input[type='range']:focus::-ms-fill-lower { + background: #161f27; + background: var(--background); + } +} + +input[type='range']:focus::-ms-fill-upper { + background: #efefef; + background: var(--background); +} + +@media (prefers-color-scheme: dark) { + + input[type='range']:focus::-ms-fill-upper { + background: #161f27; + background: var(--background); + } +} + +a { + text-decoration: none; + color: #0076d1; + color: var(--links); +} + +@media (prefers-color-scheme: dark) { + + a { + color: #41adff; + color: var(--links); + } +} + +a:hover { + text-decoration: underline; +} + +code { + background: #efefef; + background: var(--background); + color: #000; + color: var(--code); + padding: 2.5px 5px; + border-radius: 6px; + font-size: 1em; +} + +@media (prefers-color-scheme: dark) { + + code { + color: #ffbe85; + color: var(--code); + } +} + +@media (prefers-color-scheme: dark) { + + code { + background: #161f27; + background: var(--background); + } +} + +samp { + background: #efefef; + background: var(--background); + color: #000; + color: var(--code); + padding: 2.5px 5px; + border-radius: 6px; + font-size: 1em; +} + +@media (prefers-color-scheme: dark) { + + samp { + color: #ffbe85; + color: var(--code); + } +} + +@media (prefers-color-scheme: dark) { + + samp { + background: #161f27; + background: var(--background); + } +} + +time { + background: #efefef; + background: var(--background); + color: #000; + color: var(--code); + padding: 2.5px 5px; + border-radius: 6px; + font-size: 1em; +} + +@media (prefers-color-scheme: dark) { + + time { + color: #ffbe85; + color: var(--code); + } +} + +@media (prefers-color-scheme: dark) { + + time { + background: #161f27; + background: var(--background); + } +} + +pre > code { + padding: 10px; + display: block; + overflow-x: auto; +} + +var { + color: #39a33c; + color: var(--variable); + font-style: normal; + font-family: monospace; +} + +@media (prefers-color-scheme: dark) { + + var { + color: #d941e2; + color: var(--variable); + } +} + +kbd { + background: #efefef; + background: var(--background); + border: 1px solid #dbdbdb; + border: 1px solid var(--border); + border-radius: 2px; + color: #363636; + color: var(--text-main); + padding: 2px 4px 2px 4px; +} + +@media (prefers-color-scheme: dark) { + + kbd { + color: #dbdbdb; + color: var(--text-main); + } +} + +@media (prefers-color-scheme: dark) { + + kbd { + border: 1px solid #526980; + border: 1px solid var(--border); + } +} + +@media (prefers-color-scheme: dark) { + + kbd { + background: #161f27; + background: var(--background); + } +} + +img, +video { + max-width: 100%; + height: auto; +} + +hr { + border: none; + border-top: 1px solid #dbdbdb; + border-top: 1px solid var(--border); +} + +@media (prefers-color-scheme: dark) { + + hr { + border-top: 1px solid #526980; + border-top: 1px solid var(--border); + } +} + +table { + border-collapse: collapse; + margin-bottom: 10px; + width: 100%; + table-layout: fixed; +} + +table caption { + text-align: left; +} + +td, +th { + padding: 6px; + text-align: left; + vertical-align: top; + word-wrap: break-word; +} + +thead { + border-bottom: 1px solid #dbdbdb; + border-bottom: 1px solid var(--border); +} + +@media (prefers-color-scheme: dark) { + + thead { + border-bottom: 1px solid #526980; + border-bottom: 1px solid var(--border); + } +} + +tfoot { + border-top: 1px solid #dbdbdb; + border-top: 1px solid var(--border); +} + +@media (prefers-color-scheme: dark) { + + tfoot { + border-top: 1px solid #526980; + border-top: 1px solid var(--border); + } +} + +tbody tr:nth-child(even) { + background-color: #efefef; + background-color: var(--background); +} + +@media (prefers-color-scheme: dark) { + + tbody tr:nth-child(even) { + background-color: #161f27; + background-color: var(--background); + } +} + +tbody tr:nth-child(even) button { + background-color: #f7f7f7; + background-color: var(--background-alt); +} + +@media (prefers-color-scheme: dark) { + + tbody tr:nth-child(even) button { + background-color: #1a242f; + background-color: var(--background-alt); + } +} + +tbody tr:nth-child(even) button:hover { + background-color: #fff; + background-color: var(--background-body); +} + +@media (prefers-color-scheme: dark) { + + tbody tr:nth-child(even) button:hover { + background-color: #202b38; + background-color: var(--background-body); + } +} + +::-webkit-scrollbar { + height: 10px; + width: 10px; +} + +::-webkit-scrollbar-track { + background: #efefef; + background: var(--background); + border-radius: 6px; +} + +@media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-track { + background: #161f27; + background: var(--background); + } +} + +::-webkit-scrollbar-thumb { + background: rgb(170, 170, 170); + background: var(--scrollbar-thumb); + border-radius: 6px; +} + +@media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-thumb { + background: #040a0f; + background: var(--scrollbar-thumb); + } +} + +@media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-thumb { + background: #040a0f; + background: var(--scrollbar-thumb); + } +} + +::-webkit-scrollbar-thumb:hover { + background: #9b9b9b; + background: var(--scrollbar-thumb-hover); +} + +@media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-thumb:hover { + background: rgb(0, 0, 0); + background: var(--scrollbar-thumb-hover); + } +} + +@media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-thumb:hover { + background: rgb(0, 0, 0); + background: var(--scrollbar-thumb-hover); + } +} + +::-moz-selection { + background-color: #9e9e9e; + background-color: var(--selection); + color: #000; + color: var(--text-bright); +} + +::selection { + background-color: #9e9e9e; + background-color: var(--selection); + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + ::-moz-selection { + color: #fff; + color: var(--text-bright); + } + + ::selection { + color: #fff; + color: var(--text-bright); + } +} + +@media (prefers-color-scheme: dark) { + + ::-moz-selection { + background-color: #1c76c5; + background-color: var(--selection); + } + + ::selection { + background-color: #1c76c5; + background-color: var(--selection); + } +} + +details { + display: flex; + flex-direction: column; + align-items: flex-start; + background-color: #f7f7f7; + background-color: var(--background-alt); + padding: 10px 10px 0; + margin: 1em 0; + border-radius: 6px; + overflow: hidden; +} + +@media (prefers-color-scheme: dark) { + + details { + background-color: #1a242f; + background-color: var(--background-alt); + } +} + +details[open] { + padding: 10px; +} + +details > :last-child { + margin-bottom: 0; +} + +details[open] summary { + margin-bottom: 10px; +} + +summary { + display: list-item; + background-color: #efefef; + background-color: var(--background); + padding: 10px; + margin: -10px -10px 0; + cursor: pointer; + outline: none; +} + +@media (prefers-color-scheme: dark) { + + summary { + background-color: #161f27; + background-color: var(--background); + } +} + +summary:hover, +summary:focus { + text-decoration: underline; +} + +details > :not(summary) { + margin-top: 0; +} + +summary::-webkit-details-marker { + color: #363636; + color: var(--text-main); +} + +@media (prefers-color-scheme: dark) { + + summary::-webkit-details-marker { + color: #dbdbdb; + color: var(--text-main); + } +} + +dialog { + background-color: #f7f7f7; + background-color: var(--background-alt); + color: #363636; + color: var(--text-main); + border: none; + border-radius: 6px; + border-color: #dbdbdb; + border-color: var(--border); + padding: 10px 30px; +} + +@media (prefers-color-scheme: dark) { + + dialog { + border-color: #526980; + border-color: var(--border); + } +} + +@media (prefers-color-scheme: dark) { + + dialog { + color: #dbdbdb; + color: var(--text-main); + } +} + +@media (prefers-color-scheme: dark) { + + dialog { + background-color: #1a242f; + background-color: var(--background-alt); + } +} + +dialog > header:first-child { + background-color: #efefef; + background-color: var(--background); + border-radius: 6px 6px 0 0; + margin: -10px -30px 10px; + padding: 10px; + text-align: center; +} + +@media (prefers-color-scheme: dark) { + + dialog > header:first-child { + background-color: #161f27; + background-color: var(--background); + } +} + +dialog::-webkit-backdrop { + background: #0000009c; + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); +} + +dialog::backdrop { + background: #0000009c; + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); +} + +footer { + border-top: 1px solid #dbdbdb; + border-top: 1px solid var(--border); + padding-top: 10px; + color: #70777f; + color: var(--text-muted); +} + +@media (prefers-color-scheme: dark) { + + footer { + color: #a9b1ba; + color: var(--text-muted); + } +} + +@media (prefers-color-scheme: dark) { + + footer { + border-top: 1px solid #526980; + border-top: 1px solid var(--border); + } +} + +body > footer { + margin-top: 40px; +} + +@media print { + body, + pre, + code, + summary, + details, + button, + input, + textarea { + background-color: #fff; + } + + button, + input, + textarea { + border: 1px solid #000; + } + + body, + h1, + h2, + h3, + h4, + h5, + h6, + pre, + code, + button, + input, + textarea, + footer, + summary, + strong { + color: #000; + } + + summary::marker { + color: #000; + } + + summary::-webkit-details-marker { + color: #000; + } + + tbody tr:nth-child(even) { + background-color: #f2f2f2; + } + + a { + color: #00f; + text-decoration: underline; + } +}