diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..355513d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "themes/mana"] + path = themes/mana + url = https://github.com/Livour/hugo-mana-theme.git diff --git a/build.sh b/build.sh index 471ba0a..86d54da 100755 --- a/build.sh +++ b/build.sh @@ -7,5 +7,5 @@ hugo ./pagefind --site public/ # correct permissions -sudo chown -R 1000:1000 /opt/docker/website/public -sudo chmod -R 755 /opt/docker/website/public +sudo chown -R 1000:1000 public +sudo chmod -R 755 public diff --git a/config.toml b/config.toml index 41457e9..ab97159 100644 --- a/config.toml +++ b/config.toml @@ -1,27 +1,29 @@ baseURL = "https://pyte.dev" -languageCode = "en-us" -title = "PyteDev Blog" -theme = "PaperMod" +languageCode = "de-DE" +title = "Demians Blog" +theme = "mana" -[params.homeInfoParams] -Title = "Hey, I’m Demian 👋" -Content = "I am a Sysadmin, Email Enthusiast, and Open Source Contributor. I enjoy designing hosting solutions and contributing to open source projects. On this blog, I mostly write about things I've learned throughout my journey, as well as curious topics related to system administration, open source technology, and the web. I'm an active contributor to the [ISPConfig](https://ispconfig.org) project and am currently getting involved with [KDE](https://kde.org)." +[params] +description = "Mein persönlicher Blog über Open-Source Technologien, Hostinglösungen, Linux und den ganzen Rest." +favicon = "favicon.ico" +footerText = "Built with Hugo and Mana ❤️" +# Hero section title (optional - defaults to site.Title) +heroTitleLine1 = "Demians" # First line of hero title +heroTitleLine2 = "Blog" # Second line of hero title (optional) -[[params.socialIcons]] -name = "matrix" -url = "https://matrix.to/#/@pytedev:matrix.org" +[params.avatar] +url = "https://pyte.dev/assets/patrick.png" -[[params.socialIcons]] -name = "email" -url = "mailto:public@pyte.dev" +[params.social] +github = "https://github.com/pyte1" +email = "demian (at) pyte (dot) dev" -[[params.socialIcons]] -name = "github" -url = "https://github.com/pyte1" +[params.assets] +favicon = "/assets/favicon.ico" -[[params.socialIcons]] -name = "mastodon" -url = "https://mastodon.social/pytedev" +#[params.homeInfoParams] +#Title = "Hey, I’m Demian 👋" +#Content = "I am a Sysadmin, Email Enthusiast, and Open Source Contributor. I enjoy designing hosting solutions and contributing to open source projects. On this blog, I mostly write about things I've learned throughout my journey, as well as curious topics related to system administration, open source technology, and the web. I'm an active contributor to the [ISPConfig](https://ispconfig.org) project and am currently getting involved with [KDE](https://kde.org)." [[menu.main]] identifier = "home" @@ -35,8 +37,15 @@ name = "Blog" url = "/posts/" weight = 20 -[[menu.main]] -identifier = "search" -name = "Search" -url = "/search/" -weight = 30 +[markup.highlight] +codeFences = true +guessSyntax = true +noClasses = false +style = "catppuccin-macchiato" # Default style + +[params.codeHighlight] +darkTheme = "catppuccin-macchiato" # Theme for dark mode +lightTheme = "catppuccin-frappe" # Theme for light mode + +[outputs] +home = ["HTML", "JSON"] diff --git a/content/posts/blocking-invalid-rcpt-postfix.md b/content/posts/blocking-invalid-rcpt-postfix.md new file mode 100644 index 0000000..035d8f4 --- /dev/null +++ b/content/posts/blocking-invalid-rcpt-postfix.md @@ -0,0 +1,126 @@ ++++ +date = '2025-08-01T10:03:15+02:00' +draft = false +title = 'Blocking Invalid Recipients Before They Reach Your Exchange Server' + + +[cover] +image = "/imgs/email-route.jpg" +alt = "Email App icon on blue background" +caption = "" ++++ + +Recently, I had to deal with a serious problem: **backscatter**. +One of our mail gateways ended up listed on the **backscatter.org** blacklist for sending bounce messages to forged senders. + +After checking the logs, I quickly realized that our system wasn't actually protected against backscatter attacks, so I had to do something about it. + + + +## What Even Is Backscatter? + +Backscatter is unwanted email that your mail server sends **after** receiving a message, usually in the form of a **non-delivery report (NDR)** or **bounce** to a **forged sender address**. + +It happens when: + +- A spammer sends email with a **fake "From" address** (often an innocent third party). +- Your mail server **accepts** the message first, but later discovers it’s undeliverable. +- Your server sends a **bounce** to the forged address, hitting an innocent person instead of the spammer. + +This makes your server appear to be sending spam, even though you're just bouncing bad mail. + + + +## The Problem in Our Setup + +Our mail gateway mostly relays mail to multiple internal Exchange Server clusters. +We have a list of valid domains configured, so Postfix will only accept mail for those domains. + +**The problem?** +Postfix didn't know which individual recipients were valid on the downstream Exchange servers. +That meant it would happily accept messages for **nonexistent users**, only to later bounce them, classic backscatter behavior. + + + +## The Goal + +We needed to reject mail **during the SMTP session**, ideally right after the `RCPT TO` command, if the recipient didn't exist on the Exchange servers. + +That way, the sending server would get the rejection immediately, and we would never have to generate a bounce message. + + + +## Possible Solutions I Considered + +### **1. relay_recipient_maps** +This would require maintaining a **full list of valid recipients** in a Postfix lookup table. +It works well, but means writing and maintaining a script to **sync the list from Active Directory** on a regular basis. + +For us, this was too much custom scripting, too fragile, and too messy to maintain. + + + +### **2. virtual_mailbox_maps with LDAP** +Another approach would be using **LDAP lookups** directly against Active Directory to verify recipients in real-time. + +This can work in some setups, but: +- It adds complexity and dependencies. +- It can introduce security concerns. +- It didn’t fit well with our environment. + +So I ruled it out. + + + +## The Solution: `reject_unverified_recipient` + +While reading through the **Postfix Address Verification Howto**, I came across the `reject_unverified_recipient` option - bling! - exactly what I needed. + + + +### **How It Works** + +When an incoming SMTP session reaches the `RCPT TO` stage: + +1. Postfix checks if `reject_unverified_recipient` is enabled for the recipient domain. +2. If yes, it temporarily **probes the downstream mail system** to see if the recipient address exists. +3. If the downstream system says: + - **User exists** → Postfix continues processing. + - **User does not exist** → Postfix **rejects immediately** with: + ``` + 550 5.1.1 : Recipient address rejected: User unknown + ``` + +Because the rejection happens **during SMTP**, no bounce is generated, and backscatter is avoided entirely. + + +## Implementation in ISPConfig + +In our case, the mail gateways run **Postfix** with **ISPConfig** as the management interface. +I implemented a new configuration option in ISPConfig for **per-domain control** of `reject_unverified_recipient`, along with a **validation server** to specify for the downstream validation server. + +### Specifying a Validation Server for Recipient Verification + +One important detail is that Exchange servers cannot validate recipients over the default SMTP transport on port `25`. To enable recipient validation, you need to activate it on the Exchange server and use the Hub Transport service, which by default runs on port `2525`. + +Make sure to restrict access to this port, as it requires anonymous login specifically for recipient validation, you don't want that exposed broadly. + +> **Note:** This setup does not affect your regular mail flow. Only the SMTP probes used for verifying recipients are sent to this validation server. +> Postfix enables this behavior with the `address_verify_transport_maps` option. + +To optimize performance and reduce unnecessary verification probes for addresses that have already been checked, I configured a local cache on the mail gateway using the `address_verify_map` option. This way, repeated probes for the same recipient are avoided. + +Now, for domains that need recipient verification, we can enable it in the panel and point Postfix to the appropriate **validation transport**. + + +## The Result + +After enabling `reject_unverified_recipient` and pointing it at the Exchange clusters, the backscatter stopped completely. +We were no longer accepting messages for invalid recipients. + +## Conclusion + +If your Postfix server is acting as a relay for Exchange (or any downstream mail system) and you're struggling with **backscatter spam**, enabling `reject_unverified_recipient` can be a clean and effective fix. + +It avoids maintaining large static recipient maps, works dynamically, and ensures that invalid mail is rejected **before** it ever gets into your system. + diff --git a/content/posts/dovecot-index-cache-issues.md b/content/posts/dovecot-index-cache-issues.md new file mode 100644 index 0000000..6939a46 --- /dev/null +++ b/content/posts/dovecot-index-cache-issues.md @@ -0,0 +1,73 @@ ++++ +date = '2025-05-18T11:34:09+02:00' +draft = false +title = 'Dovecot Index Cache Issues' + +[cover] +image = "/imgs/dove.jpg" +alt = "Bunch of Doves on a roof" +caption = "" + ++++ + +# Understanding `dovecot.index.cache` + +I recently migrated an old mail server system into its new home. After the migration, I checked the logs and noticed some warnings that looked like this: +``` +May 17 11:23:13 server1 dovecot: dsync-local(user@domain.tld): Error: Mailbox INBOX: mmap(size=511310568) failed with file /var/vmail/domain.tld/user/Maildir/dovecot.index.cache: Cannot allocate memory +``` +The error indicates that the `dovecot.index.cache` file is too big to process, and Dovecot cannot allocate enough memory to handle it. + +# What Are `dovecot.index.cache` Files? + +Dovecot, the most popular IMAP server, uses a set of index files (`dovecot.index`, `dovecot.index.cache`, `dovecot.index.log`, etc.) to speed up mailbox access. The file I had to deal with stores cached message metadata (headers, flags, and preview text) so Dovecot does not have to read each message file in the mailbox every time. + +Over time, the `dovecot.index.cache` file can grow very large or become outdated. Here are a few reasons why this happens: + +### During Normal Operation + +- Deleted or moved messages may leave behind unused metadata. +- Corrupt or unreferenced entries might accumulate if a process is interrupted. +- Stale data can hang around for years if not explicitly purged. + +### After a Migration + +Migrations are particularly prone to creating out-of-sync or bloated index files because: + +- **File timestamps and UIDs change**: Dovecot's cache is based on assumptions about message state. A migration (e.g. via `rsync` or `imapsync`) may change those assumptions, causing the cache to mismatch the actual message data. +- **Index format/version mismatch**: If you switch Dovecot versions between servers, the format of `.index.cache` might be incompatible or inefficient. +- **Partial cache rebuilds**: After a migration, Dovecot may try to reuse old cache files that no longer reflect the real contents of the mailbox, leading to odd behavior or performance issues. + +# How to Deal with These Files + +After migrating to the new servers, I encountered a few of these messages in the log, so I had to search through and find all users affected by this. Since these large files not only consume unnecessary disk space but also affect performance and cause `dsync` issues, I had to do something about it. + +## Is It Safe to Delete These Files? + +Short answer: **Yes** — it's safe to delete all `dovecot.index*` files inside a user's Maildir. + +Dovecot will automatically regenerate the indexes when needed. This will result in a small delay the first time a user accesses their mailbox after deleting these files, but performance will return to normal quickly. + +## How to Find and Clean Up Large Index Files + +First, you should check what "large" means for your setup. I checked for files that are larger than 100 MB in size using the following command: +```bash +find /var/vmail -type f -name "dovecot.index.cache" -size +100M -exec ls -lh {} \; +``` +After validating these files and users, I then moved into the user’s Maildir and removed the files: +``` +cd /var/vmail/domain.tld/affecteduser/Maildir/ +rm dovecot.index* +``` +## What Else Can I Do? + +There’s another way to deal with large index files: you could increase the virtual memory limit that Dovecot is allowed to use, which is controlled by the `default_vsz_limit` in Dovecot’s configuration files. + +Another option to prevent future bloat of these files would be adjusting the `mail_cache_fields` and `mail_never_cache_fields` in the configuration. + +I decided to delete these files for affected users because these files had grown so large before migrating to the new setup. After regenerating, the files are much smaller, and time will tell if this becomes an issue again. + +# Conclusion + +If you encounter these warnings in your logs, it's worth checking the `dovecot.index.cache` files and dealing with them. Cleaning them up is safe and easy — Dovecot will take care of rebuilding what it needs. + diff --git a/content/posts/google-groups-spam.md b/content/posts/google-groups-spam.md new file mode 100644 index 0000000..de2a9df --- /dev/null +++ b/content/posts/google-groups-spam.md @@ -0,0 +1,116 @@ ++++ +date = '2025-10-10T09:26:56+02:00' +draft = false +title = 'Google Groups Spam' + +[cover] +image = "/imgs/google-spam.jpg" +alt = "Google in bright colors and dark background" +caption = "" ++++ + +# Understanding the Google Groups Spam Problem + +Over the past year, I've noticed a significant increase in spam messages originating from Google Groups. This issue stems from the way Google Groups is designed: It gives spammers an easy way to distribute large volumes of unwanted mail using legitimate Google mail servers, which makes filtering much harder. + +## Why it Happens + +There are a few fundamental problems with how Google Groups works that make it particularly attractive to spammers: + +- **No opt-in required**: Spammers can freely add any email address to a Google Group without the recipient's consent. + +- **Unsubscribing is often impossible**: Many spam groups are set to private, meaning that victims cannot even access the group page to unsubscribe. + +- **Amplified spam through auto-responders**: Some of these groups include automated mail systems (like ticketing or vacation responders). When these systems reply to the initial spam message, the responses are redistributed to all group members multiplying the traffic in unwanted mails. + +## The Challenge and Approach + +While analyzing these messages, I discovered that several legitimate organizations also use Google Groups to distribute newsletters or announcements. That means simply blocking all mail coming from Google Groups would cause false positives and disrupt valid communication. + +To handle this more effectively, I developed a solution that integrates directly with Rspamd, our spam filtering system: + +- **Custom Lua plugin**: Detects messages originating from Google Groups and assigns a custom symbol. + +- **Composite rules**: Use this symbol, combined with other spam indicators, to decide whether a message should be classified as spam. + +This approach allows us to target the abusive patterns specifically, without penalizing legitimate use of Google Groups. + +## Technical Configuration + +To tackle the Google Groups spam issue, I built a small custom Lua plugin for Rspamd that detects messages originating from Google Groups and assigns a custom symbol to them. Once tagged, we can use composite rules to decide whether a message should be treated as spam based on additional indicators. + +### Step 1: Create the Custom Lua Plugin + +Start by creating a new file at `/etc/rspamd/plugins.d/kits_header_google_group.lua`: + +```lua +rspamd_config:register_symbol{ + name = "KITS_HEADER_GOOGLE_GROUP", + score = 0.1, + group = "headers", + description = "Message contains X-Google-Group-Id header or List-Unsubscribe header with googlegroups", + callback = function(task) + -- Check for X-Google-Group-Id header + if task:get_header('X-Google-Group-Id') then + return true + end + + -- Check for List-Unsubscribe header containing 'googlegroups' + local list_unsubscribe = task:get_header('List-Unsubscribe') + if list_unsubscribe and string.find(list_unsubscribe:lower(), 'googlegroups') then + return true + end + + return false + end +} +``` + +This plugin checks for either of the following headers: + +- `X-Google-Group-Id` +- `List-Unsubscribe` containing the string `googlegroups` + +If either is present, the message is tagged with the symbol `KITS_HEADER_GOOGLE_GROUP`. + +### Step 2: Enable the Plugin + +Next, register a module with the same name by creating an empty configuration file at `/etc/rspamd/modules.d/kits_header_google_group.conf`: + +```bash +# Empty config to enable lua plugin +kits_header_google_group { } +``` + +At this point, every email that originates from a Google Group will be tagged with the symbol `KITS_HEADER_GOOGLE_GROUP` and adds a score of 0.1. + +### Step 3: Create Composite Rules + +The next step is to define composite rules that evaluate whether a tagged message is likely to be spam. Create the following entries in `/etc/rspamd/override.d/composite.conf`: + +```bash +# Google Group origin with bulk or freemail origin +KITS_GOOGLE_GROUP_BAD { + expression = "KITS_HEADER_GOOGLE_GROUP and (DCC_REJECT | FUZZY_BULK | FREEMAIL_FROM)"; + score = 8.0; +} + +# Google Group origin with bulk and freemail origin +KITS_GOOGLE_GROUP_WORST { + expression = "KITS_HEADER_GOOGLE_GROUP and (DCC_REJECT | FUZZY_BULK ) and FREEMAIL_FROM"; + score = 20.0; +} +``` + +These rules assign higher scores to messages that originate from Google Groups and match known spam indicators such as `DCC_REJECT`, `FUZZY_BULK`, or `FREEMAIL_FROM`. + +> The naming scheme, scores, and logic here are tailored to my environment. Always adjust the scoring and expressions to fit your setup and verify changes before applying them. + +## Conclusion + +After deploying this configuration, the amount of spam originating from Google Groups dropped noticeably. Legitimate messages from companies still passed through correctly, while unwanted group spam was effectively flagged or quarantined by Rspamd. + +If you're running a mail server, fighting spam is one of the more tedious and ongoing challenges. Thankfully, with Rspamd, we have some of the best and most flexible spam-fighting tools available. + +Battling spam will always be a cat-and-mouse game, and I'm sure spammers will find new and clever ways to distribute unwanted mail sooner rather than later, but Rspamd will be here to help. I hope to share more useful posts in the future about keeping our mail systems clean with Rspamd. + diff --git a/content/posts/spaceship-distrobox.md b/content/posts/spaceship-distrobox.md new file mode 100644 index 0000000..4685505 --- /dev/null +++ b/content/posts/spaceship-distrobox.md @@ -0,0 +1,33 @@ ++++ +date = '2026-01-04T20:33:45+01:00' +draft = false +title = 'Spaceship Distrobox' + +[cover] +image = "/imgs/spaceship.png" +alt = "official spaceship rocket logo" +caption = "" ++++ + +# How I Solved the Distrobox Confusion in My Terminal + +Recently, I found myself struggling with something that seemed like a simple problem: when I was working with multiple terminal windows, I could never remember which one was inside a Distrobox and which one was on my local system. If you've ever juggled between different environments, you know exactly how annoying this can get. + +So, I decided to create a quick plugin for my favorite terminal prompt, **Spaceship**, that would clearly indicate when I'm inside a Distrobox container. The result is a simple, customizable section that appears right in your prompt, showing the name of the active container. This way, I never have to guess or dig deeper to figure out where I'm working. + +## Why This Plugin Exists + +While there are already tools and functions for **Bash** or even the **Starship Prompt** to detect and display if you're inside a container, I couldn't find anything that suited my needs for **Spaceship Prompt**. I wanted a minimal, easy to install plugin that would do exactly what I needed: show me when I'm in a Distrobox without cluttering up my prompt. + +That's why I created this plugin. It's simple, lightweight, and specifically designed for Spaceship users who want to easily track which environment they're in without extra hassle. + +### Key Features: + +- **Instant Visual Cue**: It's super clear when I'm inside a Distrobox. No need to check `CONTAINER_ID` or other variables. +- **Customizable**: I can change the symbol, the color, and the position in the prompt to match my setup. +- **Super Simple Setup**: I'm already using the Spaceship prompt, so adding this feature took just a couple of lines of code. + +### Where to get it + +For installation and configuration instructions, check out the [Spaceship Distrobox plugin Repository](https://github.com/pyte1/spaceship-distrobox). + diff --git a/favicon/favicon.ico b/favicon/favicon.ico new file mode 100644 index 0000000..68ec2ac Binary files /dev/null and b/favicon/favicon.ico differ diff --git a/post.sh b/post.sh index 5be3e7c..e3cd597 100755 --- a/post.sh +++ b/post.sh @@ -4,8 +4,9 @@ POSTNAME=$1 if [[ -z $1 ]]; then echo "needs argument: postname" + exit 1 fi -hugo new content content/posts/${$POSTNAME}.md +hugo new content content/posts/${POSTNAME}.md -echo "Create content/posts/${$POSTNAME}.md. Remember to set the draft flag correctly!" +echo "Create content/posts/${POSTNAME}.md. Remember to set the draft flag correctly!" diff --git a/public/404.html b/public/404.html index e44deea..90446c0 100755 --- a/public/404.html +++ b/public/404.html @@ -1,202 +1,385 @@ - - - - - - - -404 Page not found | PyteDev Blog - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - -
- -
-
-
404
-
+404 Page not found | Demians Blog + - © 2025 PyteDev Blog · + + "> - - Powered by - Hugo & - PaperMod - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skip to main content + + +
+ + +
+
+
+
+

+ 404 + This Page Does Not Exist +

+

+ > + + Sorry, the page you are looking for could not be found. + +

+ +
+
+ +
+
+
+
+ +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/favicon.ico b/public/assets/favicon.ico new file mode 100755 index 0000000..95a79f2 Binary files /dev/null and b/public/assets/favicon.ico differ diff --git a/public/assets/patrick.png b/public/assets/patrick.png new file mode 100755 index 0000000..441bc31 Binary files /dev/null and b/public/assets/patrick.png differ diff --git a/public/assets/wget-log b/public/assets/wget-log new file mode 100755 index 0000000..f1d564b --- /dev/null +++ b/public/assets/wget-log @@ -0,0 +1,6 @@ +--2026-01-13 18:39:18-- https://i.redd.it/characters-who-are-dumb-but-dont-have-a-heart-to-make-up-v0-46o9rs25apke1.png?width=864 +Resolving i.redd.it (i.redd.it)... 146.75.121.140, 2a04:4e42:8e::396 +Connecting to i.redd.it (i.redd.it)|146.75.121.140|:443... connected. +HTTP request sent, awaiting response... 403 Forbidden +2026-01-13 18:39:18 ERROR 403: Forbidden. + diff --git a/public/categories/index.html b/public/categories/index.html index ee7f3fb..8ccd523 100755 --- a/public/categories/index.html +++ b/public/categories/index.html @@ -1,208 +1,501 @@ - - - - - - - -Categories | PyteDev Blog - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - -
- -
-
- - -
    -
-
+Categories | Demians Blog + - © 2025 PyteDev Blog · + + "> - - Powered by - Hugo & - PaperMod - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skip to main content + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ POSTS +

+ +
+ 0 + posts + +
+ + +
+ + +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ + +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/public/categories/index.xml b/public/categories/index.xml index b52073f..73f47c5 100755 --- a/public/categories/index.xml +++ b/public/categories/index.xml @@ -1,11 +1,11 @@ - + - Categories on PyteDev Blog + Categories on Demians Blog https://pyte.dev/categories/ - Recent content in Categories on PyteDev Blog - Hugo -- 0.147.2 - en-us + Recent content in Categories on Demians Blog + Hugo + de-DE diff --git a/public/categories/page/1/index.html b/public/categories/page/1/index.html new file mode 100755 index 0000000..9cea044 --- /dev/null +++ b/public/categories/page/1/index.html @@ -0,0 +1,9 @@ + + + + https://pyte.dev/categories/ + + + + + diff --git a/public/css/bundle.min.783a79746a859af9be598cbc33fba2a1087434b23768524277b07ef28336f113.css b/public/css/bundle.min.783a79746a859af9be598cbc33fba2a1087434b23768524277b07ef28336f113.css new file mode 100755 index 0000000..c6b22bf --- /dev/null +++ b/public/css/bundle.min.783a79746a859af9be598cbc33fba2a1087434b23768524277b07ef28336f113.css @@ -0,0 +1 @@ +.skip-to-main{position:absolute;top:-100px;left:0;background:var(--accent-primary);color:var(--bg-deep);padding:var(--spacing-sm)var(--spacing-md);text-decoration:none;font-weight:600;z-index:1000;border-radius:0 0 .25rem 0;transition:top .2s ease;clip:rect(0,0,0,0);clip-path:inset(50%);width:1px;height:1px;overflow:hidden}.skip-to-main:focus{top:0;clip:auto;clip-path:none;width:auto;height:auto;overflow:visible}body{overflow-x:hidden;position:relative}code,pre{font-family:var(--font-mono)}.glass-effect{background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--border-color);border-radius:.5rem}.card-base{background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color);transition:all var(--transition-base)}.card-base:hover{box-shadow:var(--shadow-md);border-color:var(--accent-primary)}.icon-sm{width:var(--icon-sm);height:var(--icon-sm)}.icon-md{width:var(--icon-md);height:var(--icon-md)}.icon-lg{width:var(--icon-lg);height:var(--icon-lg)}body{overflow-x:hidden}code,pre{font-family:var(--font-mono)}.glass-effect{background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--border-color);border-radius:.5rem}.card-base{background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color);transition:all var(--transition-base)}.card-base:hover{box-shadow:var(--shadow-md);border-color:var(--accent-primary)}.icon-sm{width:var(--icon-sm);height:var(--icon-sm)}.icon-md{width:var(--icon-md);height:var(--icon-md)}.icon-lg{width:var(--icon-lg);height:var(--icon-lg)}main{max-width:1200px;margin:0 auto;padding:var(--spacing-xl)var(--spacing-lg);min-height:calc(100vh - 200px)}.sticky-header{position:sticky;top:0;z-index:100;background-color:var(--bg-primary);border-bottom:1px solid var(--border-color);backdrop-filter:blur(10px);background-color:rgba(15,23,42,.8);transition:all var(--transition-base)}[data-theme=light] .sticky-header{background-color:rgba(255,255,255,.8)}.header-container{max-width:1200px;margin:0 auto;padding:0 var(--spacing-lg);box-sizing:border-box}.header-nav{display:flex;align-items:center;justify-content:space-between;padding:var(--spacing-md)0}.header-content{display:flex;align-items:center;justify-content:space-between;width:100%;min-width:0;gap:var(--spacing-sm);box-sizing:border-box}.header-logo{flex-shrink:1;min-width:0;overflow:hidden}.header-logo .logo-link{font-family:var(--font-logo);font-size:1.25rem;font-weight:700;color:var(--text-primary);text-decoration:none;transition:color var(--transition-fast);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block}.header-logo .logo-link:hover{color:var(--accent-primary)}.header-menu{display:flex;align-items:center;gap:var(--spacing-md);flex-shrink:0}.menu-list{display:flex;list-style:none;margin:0;padding:0;gap:var(--spacing-md)}.menu-link{color:var(--text-secondary);text-decoration:none;font-weight:500;padding:var(--spacing-sm)var(--spacing-md);border-radius:.375rem;transition:all var(--transition-fast)}.menu-link:hover{color:var(--accent-primary);background-color:var(--bg-secondary)}.menu-link.active{color:var(--accent-light-purple);background-color:var(--bg-secondary)}.search-toggle{background:0 0;border:none;color:var(--text-secondary);cursor:pointer;padding:var(--spacing-sm);display:flex;align-items:center;transition:color var(--transition-fast)}.search-toggle:hover{color:var(--accent-primary)}.search-icon{width:var(--icon-md);height:var(--icon-md)}.theme-toggle{background:0 0;border:none;color:var(--text-secondary);cursor:pointer;padding:var(--spacing-sm);display:flex;align-items:center;transition:color var(--transition-fast)}.theme-toggle:hover{color:var(--accent-primary)}.theme-toggle .icon-sun,.theme-toggle .icon-moon{width:1.25rem;height:1.25rem}[data-theme=light] .icon-sun{display:none}[data-theme=light] .icon-moon{display:block}.icon-sun{display:block}.icon-moon{display:none}.hero-section{width:100%;padding:var(--spacing-2xl)var(--spacing-lg);margin-bottom:var(--spacing-2xl)}.hero-container{max-width:1200px;margin:0 auto}.hero-content{display:grid;grid-template-columns:1fr 1fr;gap:var(--spacing-2xl);align-items:center}.hero-text{display:flex;flex-direction:column;gap:var(--spacing-md)}.hero-title{display:flex;flex-direction:column;gap:0;margin:0;line-height:1.1}.hero-title-line1{font-family:var(--font-heading);font-size:clamp(2.5rem,5vw,4.5rem);font-weight:900;color:var(--text-primary);letter-spacing:-.02em}.hero-title-line2{font-family:var(--font-heading);font-size:clamp(2.5rem,5vw,4.5rem);font-weight:900;color:transparent;-webkit-text-stroke:2px var(--accent-light-purple);text-stroke:2px var(--accent-light-purple);letter-spacing:-.02em}.hero-subtitle{font-family:var(--font-mono);font-size:clamp(.875rem,1.5vw,1.125rem);color:var(--text-secondary);margin:var(--spacing-md)0 0;opacity:.8}.hero-image{display:flex;justify-content:center;align-items:center}.hero-logo{max-width:100%;height:auto;filter:drop-shadow(0 0 10px var(--accent-primary-rgba-03));animation:float 3s ease-in-out infinite}.hero-logo-placeholder{width:200px;height:200px;background:var(--glass-surface);border-radius:50%;border:2px solid var(--glass-border);filter:drop-shadow(0 0 20px var(--accent-primary-rgba-05));animation:float 3s ease-in-out infinite}@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-20px)}}.home-container{max-width:900px;margin:0 auto}.posts-section{margin-top:var(--spacing-2xl)}.posts-title{font-size:1.5rem;font-weight:700;margin-bottom:var(--spacing-lg);color:var(--text-primary)}.posts-list{display:flex;flex-direction:column;gap:var(--spacing-xl)}.post-card{display:flex;gap:var(--spacing-lg);padding:var(--spacing-lg);cursor:pointer;position:relative;background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color);transition:all var(--transition-base)}.post-card:hover{box-shadow:var(--shadow-md);border-color:var(--accent-primary)}.post-image{flex-shrink:0;width:200px;height:150px;overflow:hidden;border-radius:.375rem}.post-image img{width:100%;height:100%;object-fit:cover}.post-content{flex:1;display:flex;flex-direction:column;gap:var(--spacing-sm)}.post-title{margin:0;font-size:1.5rem;font-weight:700}.post-title a{color:var(--text-primary);text-decoration:underline;text-decoration-color:transparent;text-underline-offset:.25em;transition:text-decoration-color var(--transition-base),color var(--transition-base)}.post-card:hover .post-title a{text-decoration-color:var(--accent-primary)}.post-description{color:var(--text-secondary);margin:0;line-height:1.6}.post-meta-inline{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing-sm);font-size:.875rem;color:var(--text-muted)}.post-meta-inline .post-date,.post-meta-inline .post-reading-time,.post-meta-inline .post-word-count{display:flex;align-items:center;gap:.375rem}.post-meta-inline .meta-icon{width:var(--icon-sm);height:var(--icon-sm);flex-shrink:0}.post-meta-inline .meta-separator{color:var(--text-muted);opacity:.5}.post-tags{display:flex;flex-wrap:wrap;gap:var(--spacing-sm);margin-top:var(--spacing-sm)}.post-tag{padding:.25rem .625rem;background-color:var(--tag-bg);color:var(--tag-color);border:none;border-radius:.25rem;font-size:.875rem;font-weight:400;font-family:var(--font-mono);text-decoration:none;transition:all var(--transition-fast);position:relative;z-index:10;cursor:pointer;display:inline-block}.post-tag:hover{background-color:var(--accent-light-purple-rgba-01);color:var(--tag-color);box-shadow:0 0 8px var(--accent-light-purple-rgba-03)}.single-post-wrapper{display:flex;gap:var(--spacing-xl);max-width:1200px;margin:0 auto;align-items:flex-start}.single-post{flex:1;min-width:0;max-width:800px;overflow-wrap:break-word;word-wrap:break-word}.post-toc{position:sticky;top:calc(var(--spacing-lg) + 60px);width:260px;flex-shrink:0;padding:0;background-color:var(--bg-deep);background-image:radial-gradient( circle at center,rgba(30,58,138,.3) 0%,rgba(15,23,42,.5) 40%,var(--bg-deep) 100% );background-attachment:fixed;background-size:100% 100%;background-position:50%;border:1px solid var(--border-color);border-radius:.5rem;overflow:hidden;z-index:50}.post-toc::before{content:"";position:absolute;inset:0;background-color:var(--bg-secondary);border-radius:.5rem;pointer-events:none;z-index:1}[data-theme=light] .post-toc{background-image:radial-gradient( circle at center,rgba(196,133,246,.2) 0%,rgba(243,232,255,.4) 40%,#ffffff 100% )}.toc-toggle{width:100%;display:flex;align-items:center;gap:var(--spacing-sm);padding:var(--spacing-md);background-color:initial;border:none;color:var(--text-primary);font-size:.875rem;font-weight:600;cursor:pointer;transition:all var(--transition-base);text-align:left}.toc-toggle:hover{background-color:var(--accent-mid-purple-rgba-05)}.toc-burger-icon{width:var(--icon-md);height:var(--icon-md);flex-shrink:0;color:var(--accent-primary)}.toc-toggle-text{flex:1;text-transform:uppercase;letter-spacing:.05em;font-size:.75rem;color:var(--text-muted)}.toc-chevron-icon{width:var(--icon-sm);height:var(--icon-sm);flex-shrink:0;color:var(--text-muted);transition:transform var(--transition-base)}.toc-toggle[aria-expanded=true] .toc-chevron-icon{transform:rotate(180deg)}.toc-content{max-height:0;overflow:hidden;transition:max-height .3s cubic-bezier(.4,0,.2,1)}.toc-content.expanded{max-height:calc(100vh - 200px);overflow-y:auto}.toc-content::-webkit-scrollbar{width:4px}.toc-content::-webkit-scrollbar-track{background:0 0}.toc-content::-webkit-scrollbar-thumb{background-color:var(--border-color);border-radius:2px}.toc-content::-webkit-scrollbar-thumb:hover{background-color:var(--text-muted)}.toc-nav{font-size:.8125rem;line-height:1.8;padding:0 var(--spacing-md)var(--spacing-md)}.toc-nav ul{list-style:none;padding-left:0;margin:0}.toc-nav li{margin-bottom:.375rem}.toc-nav a{color:var(--text-secondary);text-decoration:none;display:block;padding:.25rem .5rem;transition:all var(--transition-fast);border-left:2px solid transparent;border-radius:0 .25rem .25rem 0}.toc-nav a:hover{color:var(--text-primary);background-color:var(--accent-mid-purple-rgba-05)}.toc-nav a.active{color:var(--accent-primary);border-left-color:var(--accent-primary);background-color:var(--accent-mid-purple-rgba-01)}.toc-nav ul ul{margin-top:.125rem;padding-left:var(--spacing-sm);margin-left:.5rem;border-left:1px solid var(--border-color)}.toc-nav ul ul a{font-size:.75rem;padding-left:.5rem;color:var(--text-muted)}.toc-nav ul ul a:hover{color:var(--text-secondary)}.toc-nav ul ul a.active{color:var(--accent-primary)}.post-image-single{width:100%;max-width:100%;margin-bottom:var(--spacing-xl);border-radius:.5rem;overflow:hidden;border:1px solid var(--border-color);box-shadow:var(--shadow-md);background-color:var(--bg-secondary);padding:.25rem}.post-image-single img{width:100%;height:auto;display:block;object-fit:cover;border-radius:.375rem}.post-header{margin-bottom:var(--spacing-xl);padding-bottom:var(--spacing-lg);border-bottom:1px solid var(--border-color)}.post-title-main{font-size:2.5rem;font-weight:700;margin:0 0 var(--spacing-md);color:var(--text-primary);line-height:1.2}.post-description-main{font-size:1.25rem;color:var(--text-secondary);margin:0 0 var(--spacing-lg)}.post-meta{display:flex;flex-direction:column;gap:var(--spacing-md)}.post-meta-info{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing-sm);font-size:.875rem;color:var(--text-muted)}.post-date,.post-reading-time,.post-word-count{display:flex;align-items:center;gap:.375rem}.meta-icon{width:var(--icon-sm);height:var(--icon-sm);flex-shrink:0}.meta-separator{color:var(--text-muted);opacity:.5}.post-content-main{font-size:1.125rem;line-height:1.8;color:var(--text-primary);overflow-wrap:break-word;word-wrap:break-word;max-width:100%;width:100%;box-sizing:border-box}.post-content-main h2{font-size:1.75rem;margin-top:var(--spacing-xl);margin-bottom:var(--spacing-md);color:var(--text-primary)}.post-content-main h3{font-size:1.5rem;margin-top:var(--spacing-lg);margin-bottom:var(--spacing-sm);color:var(--text-primary)}.post-content-main p{margin-bottom:var(--spacing-md)}.post-content-main ul,.post-content-main ol{margin-bottom:var(--spacing-md);padding-left:1.5em;margin-left:0}.post-content-main li{margin-bottom:.5em;line-height:1.8}.post-content-main ul ul,.post-content-main ol ol,.post-content-main ul ol,.post-content-main ol ul{margin-top:.5em;margin-bottom:.5em;padding-left:1.5em}.post-content-main a{color:var(--accent-primary);text-decoration:underline;transition:color var(--transition-fast)}.post-content-main a:hover{color:var(--accent-hover)}.post-content-main pre{font-family:var(--font-mono);overflow-x:auto;max-width:100%;word-break:normal;box-sizing:border-box;width:100%}.post-content-main code{font-family:var(--font-mono);word-wrap:break-word;overflow-wrap:break-word}.post-content-main pre code{white-space:pre;word-break:normal;overflow-wrap:normal;display:block}.post-content-main .highlight,.post-content-main pre.highlight{max-width:100%;overflow-x:auto;box-sizing:border-box;width:100%}.post-content-main .highlight pre{margin:0;padding:inherit}.post-footer{margin-top:var(--spacing-xl);padding-top:var(--spacing-lg);border-top:1px solid var(--border-color)}.post-tags-footer{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing-sm)}.tags-label{font-weight:600;color:var(--text-secondary)}.post-navigation{display:flex;justify-content:space-between;margin-top:var(--spacing-2xl);padding-top:var(--spacing-xl);border-top:1px solid var(--border-color);gap:var(--spacing-lg)}.nav-link{flex:1;padding:var(--spacing-md);background-color:var(--bg-secondary);border-radius:.375rem;text-decoration:none;transition:all var(--transition-fast)}.nav-link:hover{background-color:var(--bg-tertiary);transform:translateX(-2px)}.nav-next:hover{transform:translateX(2px)}.nav-label{display:block;font-size:.875rem;color:var(--text-muted);margin-bottom:var(--spacing-xs)}.nav-title{display:block;color:var(--text-primary);font-weight:600}.list-container{max-width:900px;margin:0 auto}.list-header{margin-bottom:var(--spacing-xl)}.list-title{display:flex;flex-direction:column;align-items:center;gap:0;margin:0 0 var(--spacing-sm);line-height:1.1;text-align:center}.list-title-line1{font-family:var(--font-heading);font-size:clamp(2.5rem,5vw,4.5rem);font-weight:900;color:var(--text-primary);letter-spacing:-.02em}.list-title-line2{font-family:var(--font-heading);font-size:clamp(2.5rem,5vw,4.5rem);font-weight:900;color:transparent;-webkit-text-stroke:2px var(--accent-light-purple);text-stroke:2px var(--accent-light-purple);letter-spacing:-.02em}.list-description{color:var(--text-secondary);margin:0}.list-count{color:var(--text-muted);font-size:.875rem;margin-top:var(--spacing-sm)}.list-count-pages{color:var(--text-secondary);font-weight:500;margin-left:var(--spacing-xs)}.filter-accordion{margin-bottom:var(--spacing-xl);overflow:hidden;background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color)}.filter-toggle-btn{width:100%;display:flex;align-items:center;justify-content:space-between;padding:var(--spacing-md)var(--spacing-lg);background-color:initial;border:none;color:var(--text-primary);font-size:.875rem;font-weight:600;cursor:pointer;transition:all var(--transition-base)}.filter-toggle-btn:hover{background-color:var(--accent-mid-purple-rgba-05)}.filter-toggle-icon{width:var(--icon-md);height:var(--icon-md);transition:transform var(--transition-base)}.filter-toggle-btn.expanded .filter-toggle-icon{transform:rotate(180deg)}.posts-filter{display:flex;flex-wrap:wrap;gap:var(--spacing-lg);align-items:flex-start;padding:0 var(--spacing-lg);max-height:0;overflow:hidden;opacity:0;transition:max-height .4s cubic-bezier(.4,0,.2,1),padding .4s cubic-bezier(.4,0,.2,1),opacity .3s ease-out}.posts-filter.expanded{max-height:300px;padding-top:var(--spacing-lg);padding-bottom:var(--spacing-lg);opacity:1}.filter-group{display:flex;flex-direction:column;gap:var(--spacing-xs);flex:1;min-width:200px}.filter-group-tags{flex:1 1 100%;min-width:100%;display:flex;flex-direction:column}.filter-label{font-size:.875rem;font-weight:600;color:var(--text-secondary);margin-bottom:var(--spacing-xs)}.tag-search-container{position:relative;margin-bottom:var(--spacing-md)}.tag-search-input{width:100%;padding:var(--spacing-sm)var(--spacing-md);background-color:var(--bg-primary);border:1px solid var(--border-color);border-radius:.375rem;color:var(--text-primary);font-size:.875rem;transition:all var(--transition-base)}.tag-search-input:focus{outline:none;border-color:var(--accent-primary);box-shadow:0 0 0 3px var(--accent-mid-purple-rgba-01)}.tag-search-results{position:absolute;top:100%;left:0;right:0;margin-top:var(--spacing-xs);background-color:var(--bg-primary);border:1px solid var(--border-color);border-radius:.375rem;box-shadow:var(--shadow-lg);max-height:200px;overflow-y:auto;z-index:10;display:none}.tag-search-result-item{padding:var(--spacing-sm)var(--spacing-md);cursor:pointer;color:var(--text-primary);transition:all var(--transition-base);border-bottom:1px solid var(--border-color)}.tag-search-result-item:last-child{border-bottom:none}.tag-search-result-item:hover,.tag-search-result-item.selected{background-color:var(--bg-secondary);color:var(--accent-primary)}.tag-search-no-results{padding:var(--spacing-sm)var(--spacing-md);color:var(--text-muted);font-size:.875rem;text-align:center}.filter-select{padding:var(--spacing-sm)2.5rem var(--spacing-sm)var(--spacing-md);background-color:var(--bg-primary);border:1px solid var(--border-color);border-radius:.375rem;color:var(--text-primary);font-size:.875rem;cursor:pointer;transition:all var(--transition-base);appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23a8a8a8'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right .75rem center;background-size:1rem}.filter-select:hover{border-color:var(--accent-primary)}.filter-select:focus{outline:none;border-color:var(--accent-primary);box-shadow:0 0 0 3px var(--accent-mid-purple-rgba-01)}.filter-tags{display:flex;flex-wrap:wrap;gap:var(--spacing-sm);margin-top:var(--spacing-xs);min-height:40px;max-height:40px;overflow-y:auto;align-content:flex-start}.filter-tag-checkbox{display:flex;align-items:center;cursor:pointer;user-select:none}.filter-tag-input{display:none}.filter-tag-label{padding:var(--spacing-xs)var(--spacing-sm);background-color:var(--bg-primary);border:1px solid var(--border-color);border-radius:.375rem;font-size:.875rem;color:var(--text-primary);transition:all var(--transition-base);cursor:pointer}.filter-tag-checkbox:hover .filter-tag-label{border-color:var(--accent-primary);background-color:var(--accent-mid-purple-rgba-05)}.filter-tag-input:checked+.filter-tag-label{background-color:var(--accent-primary);border-color:var(--accent-primary);color:#fff}.filter-clear-btn{padding:var(--spacing-sm)var(--spacing-md);background-color:initial;border:1px solid var(--border-color);border-radius:.375rem;color:var(--text-secondary);font-size:.875rem;cursor:pointer;transition:all var(--transition-base);align-self:flex-end;margin-top:calc(var(--spacing-xs) + 1.25rem)}.filter-clear-btn:hover{border-color:var(--accent-primary);color:var(--accent-primary);background-color:var(--accent-mid-purple-rgba-05)}.pagination{display:flex;justify-content:space-between;align-items:center;margin-top:var(--spacing-2xl);padding-top:var(--spacing-xl);border-top:1px solid var(--border-color)}.pagination-link{padding:var(--spacing-sm)var(--spacing-md);background-color:var(--bg-secondary);color:var(--text-primary);text-decoration:none;border-radius:.375rem;transition:all var(--transition-fast)}.pagination-link:hover{background-color:var(--accent-primary);color:#fff}.pagination-info{color:var(--text-muted);font-size:.875rem}.tags-page{max-width:900px;margin:0 auto;text-align:center}.tags-title{display:flex;flex-direction:column;align-items:center;gap:0;margin-bottom:var(--spacing-lg);line-height:1.1;text-align:center}.tags-help{margin-bottom:var(--spacing-xl);color:var(--text-muted);font-size:.875rem}.tags-cloud{padding:var(--spacing-xl);background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color)}.tags-list{display:flex;flex-wrap:wrap;gap:var(--spacing-md);justify-content:center}.tag-item{text-decoration:none}.tag-chip-large{display:flex;align-items:center;padding:.25rem .625rem;background-color:var(--tag-bg);color:var(--tag-color);border:none;border-radius:.25rem;font-size:.875rem;font-weight:400;font-family:var(--font-mono);transition:all var(--transition-fast);position:relative;z-index:10;cursor:pointer}.tag-item:hover .tag-chip-large{background-color:var(--accent-light-purple-rgba-01);color:var(--tag-color);box-shadow:0 0 8px var(--accent-light-purple-rgba-03)}.tag-name{font-weight:400;margin-right:0}.tag-count-badge{padding:.125rem .5rem;background-color:initial;color:var(--text-secondary);border-radius:.25rem;font-size:.75rem;margin-left:.5rem;opacity:.7}.tag-item:hover .tag-count-badge{background-color:initial;color:var(--text-secondary);opacity:.9}.tags-back{margin-top:var(--spacing-2xl)}.tags-back-link{display:inline-flex;align-items:center;gap:var(--spacing-sm);color:var(--text-secondary);text-decoration:none;transition:color var(--transition-fast)}.tags-back-link:hover{color:var(--accent-primary)}.back-icon{width:var(--icon-sm);height:var(--icon-sm)}.tag-page{max-width:900px;margin:0 auto}.tag-header{text-align:center;margin-bottom:var(--spacing-2xl)}.tag-header-content{display:flex;align-items:center;justify-content:center;gap:var(--spacing-sm);margin-bottom:var(--spacing-sm)}.tag-icon{width:1.25rem;height:1.25rem;color:var(--accent-primary)}.tag-title{font-size:2rem;font-weight:700;margin:0;color:var(--text-primary)}.tag-count{color:var(--text-muted);font-size:.875rem}.tag-posts-grouped{display:flex;flex-direction:column;gap:var(--spacing-2xl)}.year-group{position:relative}.year-indicator{display:flex;align-items:center;margin-bottom:var(--spacing-lg)}.year-circle{position:relative;width:4rem;height:4rem;border-radius:50%;background:linear-gradient( 135deg,var(--accent-primary),var(--accent-light-purple) );display:flex;align-items:center;justify-content:center;font-weight:700;font-size:1.25rem;color:#fff;box-shadow:var(--shadow-md)}.year-glow{position:absolute;inset:0;border-radius:50%;background-color:var(--accent-primary);opacity:.2;filter:blur(8px)}.year-text{position:relative;z-index:1}.year-count{font-size:.875rem;vertical-align:super;margin-left:.125rem;opacity:.9}.year-line{flex:1;height:2px;margin-left:var(--spacing-md);background:linear-gradient(to right,var(--accent-primary),transparent);position:relative;display:flex;align-items:center}.year-count-inline{position:absolute;left:20%;background-color:var(--bg-primary);padding:0 var(--spacing-sm);font-size:.75rem;color:var(--text-muted);font-weight:500;white-space:nowrap;transform:translateY(-50%);top:50%}.year-posts{margin-left:calc(4rem + var(--spacing-md));display:flex;flex-direction:column;gap:var(--spacing-xl)}.year-months{margin-left:0;margin-top:var(--spacing-md);display:flex;flex-direction:column;gap:var(--spacing-lg)}.month-group{display:flex;flex-direction:column;gap:var(--spacing-md)}.month-indicator{display:flex;align-items:center;margin-bottom:var(--spacing-md)}.month-text{font-size:1rem;font-weight:600;color:var(--text-primary);white-space:nowrap;margin-right:var(--spacing-sm)}.month-line{flex:1;height:1px;background:linear-gradient(to right,var(--accent-primary),transparent);position:relative;display:flex;align-items:center;opacity:.7}.month-count-inline{position:absolute;left:20%;background-color:var(--bg-primary);padding:0 var(--spacing-sm);font-size:.75rem;color:var(--text-muted);font-weight:500;white-space:nowrap;transform:translateY(-50%);top:50%}.month-posts{display:flex;flex-direction:column;gap:var(--spacing-xl)}.tag-post-card{position:relative;padding:var(--spacing-lg);background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color);transition:all var(--transition-base)}.tag-post-card:hover{box-shadow:var(--shadow-md);border-color:var(--accent-primary)}.tag-post-link{position:absolute;inset:0;z-index:10}.tag-post-header{display:flex;flex-direction:column;gap:var(--spacing-xs);margin-bottom:var(--spacing-sm)}.tag-post-title{font-size:1.25rem;font-weight:600;margin:0;color:var(--text-primary);text-decoration:underline;text-decoration-color:transparent;text-underline-offset:.25em;transition:text-decoration-color var(--transition-base),color var(--transition-base)}.tag-post-card:hover .tag-post-title{text-decoration-color:var(--accent-primary)}.tag-post-date{font-size:.875rem;color:var(--text-muted)}.tag-post-summary{color:var(--text-secondary);font-size:.875rem;margin-bottom:var(--spacing-sm);line-height:1.6}.tag-post-tags{display:flex;flex-wrap:wrap;gap:var(--spacing-xs);margin-bottom:var(--spacing-sm);position:relative;z-index:20}.tag-chip{padding:.25rem .625rem;background-color:var(--tag-bg);color:var(--tag-color);border:none;border-radius:.25rem;font-size:.875rem;font-weight:400;font-family:var(--font-mono);text-decoration:none;transition:all var(--transition-fast);position:relative;z-index:10;cursor:pointer;display:inline-block}.tag-chip:hover{background-color:var(--accent-light-purple-rgba-01);color:var(--tag-color);box-shadow:0 0 8px var(--accent-light-purple-rgba-03)}.tag-chip-active{background-color:var(--accent-light-purple-rgba-01);color:var(--tag-color);box-shadow:0 0 8px var(--accent-light-purple-rgba-03)}.tag-post-read-more{display:flex;align-items:center;gap:var(--spacing-xs);color:var(--accent-primary);font-size:.875rem;font-weight:500;transition:transform var(--transition-fast)}.tag-post-card:hover .tag-post-read-more{transform:translateX(4px)}.read-more-icon{width:var(--icon-sm);height:var(--icon-sm)}.tag-pagination{display:flex;justify-content:space-between;margin-top:var(--spacing-xl)}.pagination-btn{display:flex;align-items:center;gap:var(--spacing-sm);padding:var(--spacing-sm)var(--spacing-md);background-color:var(--bg-secondary);color:var(--text-primary);text-decoration:none;border-radius:9999px;transition:all var(--transition-fast)}.pagination-btn:hover{background-color:var(--accent-primary);color:#fff}.pagination-icon{width:var(--icon-sm);height:var(--icon-sm)}.tag-nav-links{display:flex;justify-content:center;gap:var(--spacing-xl);margin-top:var(--spacing-xl)}.tag-nav-link{color:var(--text-secondary);text-decoration:none;transition:color var(--transition-fast)}.tag-nav-link:hover{color:var(--accent-primary)}.archives-page{max-width:900px;margin:0 auto}.archives-header{margin-bottom:var(--spacing-xl)}.archives-title{font-size:2rem;font-weight:700;margin:0 0 var(--spacing-sm);color:var(--text-primary)}.archives-description{color:var(--text-secondary);margin:0}.archive-year{margin-bottom:var(--spacing-2xl)}.archive-year-title{font-size:1.5rem;font-weight:700;margin-bottom:var(--spacing-lg);color:var(--text-primary)}.archive-month{margin-bottom:var(--spacing-lg);margin-left:var(--spacing-lg)}.archive-month-title{font-size:1.125rem;font-weight:600;margin-bottom:var(--spacing-sm);color:var(--text-secondary)}.archive-posts{list-style:none;padding:0;margin:0}.archive-post-item{display:flex;gap:var(--spacing-md);padding:var(--spacing-sm)0}.archive-date{color:var(--text-muted);font-size:.875rem;min-width:60px}.archive-link{color:var(--text-primary);text-decoration:none;transition:color var(--transition-fast)}.archive-link:hover{color:var(--accent-primary)}.about-page{max-width:900px;margin:0 auto}.about-header{text-align:center;margin-bottom:var(--spacing-2xl);padding-bottom:var(--spacing-xl);border-bottom:1px solid var(--border-color)}.about-page-avatar{width:120px;height:120px;border-radius:50%;object-fit:cover;border:3px solid var(--accent-primary);margin-bottom:var(--spacing-lg)}.about-page-name{display:flex;flex-direction:column;align-items:center;gap:0;margin:0 0 var(--spacing-md);line-height:1.1;text-align:center}.about-page-description{font-size:1.25rem;color:var(--text-secondary);margin:0 0 var(--spacing-lg);font-style:italic}.about-page-social{margin-top:var(--spacing-md)}.about-content-full{font-size:1.125rem;line-height:1.8;color:var(--text-primary)}.about-content-full h2{font-size:1.75rem;margin-top:var(--spacing-xl);margin-bottom:var(--spacing-md);color:var(--text-primary)}.about-content-full p{margin-bottom:var(--spacing-md)}.about-content-full a{color:var(--accent-primary);text-decoration:underline}.about-content-full a:hover{color:var(--accent-hover)}.site-footer{margin-top:var(--spacing-2xl);padding:var(--spacing-xl)var(--spacing-lg);border-top:1px solid var(--border-color);background-color:var(--bg-secondary)}.footer-content{max-width:1200px;margin:0 auto;text-align:center}.footer-text{color:var(--text-muted);font-size:.875rem;margin-bottom:var(--spacing-md)}.footer-social{display:flex;justify-content:center;gap:var(--spacing-md)}.social-links{display:flex;gap:var(--spacing-md);justify-content:center}.social-link{color:var(--text-secondary);transition:color var(--transition-fast);display:flex;align-items:center}.social-link:hover{color:var(--accent-primary)}.social-link svg{width:var(--icon-md);height:var(--icon-md)}.scroll-to-top{position:fixed;bottom:calc(var(--spacing-lg) + 60px);right:var(--spacing-lg);width:50px;height:50px;border-radius:50%;background-color:initial;color:var(--accent-primary);border:none;cursor:pointer;display:none;align-items:center;justify-content:center;box-shadow:none;transition:all var(--transition-base);z-index:999}.scroll-to-top.visible{display:flex}.scroll-to-top:hover{background-color:initial;transform:translateY(-2px);box-shadow:none;color:var(--accent-hover)}.scroll-to-top svg{width:1.5rem;height:1.5rem}.buy-me-coffee{position:fixed;bottom:calc(var(--spacing-lg) + 60px);right:var(--spacing-lg);width:50px;height:50px;border-radius:50%;background-color:var(--yellow-accent);color:#000;display:flex;align-items:center;justify-content:center;box-shadow:var(--shadow-md);transition:all var(--transition-base);z-index:999;text-decoration:none}.buy-me-coffee:hover{background-color:var(--yellow-hover);transform:translateY(-2px);box-shadow:var(--shadow-lg)}.buy-me-coffee svg{width:1.5rem;height:1.5rem}.code-block-wrapper{position:relative;margin:var(--spacing-md)0}.code-block-copy{position:absolute;top:.5rem;right:calc(.5rem + 95px + .5rem);z-index:10;background:rgba(30,30,45,.9);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.1);border-radius:.375rem;padding:.375rem .75rem;color:var(--text-secondary);font-size:.75rem;font-weight:500;cursor:pointer;display:flex;align-items:center;gap:.375rem;transition:all var(--transition-fast);font-family:var(--font-body);white-space:nowrap}.code-block-copy:hover{background:var(--bg-tertiary);border-color:var(--accent-primary);color:var(--accent-primary)}.code-block-copy.copied{background:rgba(34,197,94,.2);border-color:rgba(34,197,94,.5);color:#22c55e}.code-block-copy svg{width:.875rem;height:.875rem;flex-shrink:0}[data-theme=light] .code-block-copy{background:rgba(255,255,255,.95);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid rgba(107,33,168,.2);color:var(--text-secondary);top:8px}[data-theme=light] .code-block-copy:hover{background:#fff;border-color:var(--accent-primary);color:var(--accent-primary)}[data-theme=light] .code-block-copy.copied{background:rgba(34,197,94,.15);border-color:rgba(34,197,94,.4);color:#16a34a}.code-block-toggle{position:absolute;top:.5rem;right:.5rem;z-index:10;background:rgba(30,30,45,.9);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.1);border-radius:.375rem;padding:.375rem .75rem;color:var(--text-secondary);font-size:.75rem;font-weight:500;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:.375rem;transition:all var(--transition-fast);font-family:var(--font-body);width:95px;white-space:nowrap;box-sizing:border-box}.code-block-toggle:hover{background:var(--bg-tertiary);border-color:var(--accent-primary);color:var(--accent-primary)}[data-theme=light] .code-block-toggle{background:rgba(255,255,255,.95);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid rgba(107,33,168,.2);color:var(--text-secondary);top:8px}[data-theme=light] .code-block-toggle:hover{background:#fff;border-color:var(--accent-primary);color:var(--accent-primary)}.code-block-toggle svg{width:.875rem;height:.875rem;transition:transform var(--transition-fast)}.code-block-wrapper.collapsed .code-block-toggle svg{transform:rotate(180deg)}.code-block-content{transition:opacity var(--transition-base);overflow:visible}.code-block-wrapper.collapsed .code-block-content{max-height:150px;overflow:hidden;opacity:.9;position:relative}.code-block-wrapper.collapsed .code-block-content::after{content:"";position:absolute;bottom:0;left:0;right:0;height:50px;background:linear-gradient(to bottom,transparent,var(--bg-primary));pointer-events:none}.post-content-main blockquote.blockquote-regular{margin:var(--spacing-md)0;padding:var(--spacing-md)var(--spacing-lg);border-left:4px solid var(--accent-primary);background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;color:var(--text-primary);position:relative}.post-content-main blockquote.blockquote-regular p{margin-bottom:0}.post-content-main blockquote.blockquote-regular p:last-child{margin-bottom:0}.post-content-main .alert{margin:var(--spacing-md)0;border-radius:.5rem;border:1px solid var(--border-color);background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);overflow:hidden;transition:all var(--transition-base)}.post-content-main .alert:hover{border-color:var(--accent-primary);box-shadow:var(--shadow-md)}.post-content-main .alert-header{display:flex;align-items:center;gap:var(--spacing-sm);padding:var(--spacing-md)var(--spacing-lg);font-weight:600;font-size:1rem;border-bottom:1px solid var(--border-color);transition:background-color var(--transition-fast)}.post-content-main .alert-collapsible .alert-header:hover{background-color:var(--bg-tertiary)}.post-content-main .alert-icon{font-size:1.25rem;flex-shrink:0;line-height:1}.post-content-main .alert-title{flex:1;color:var(--text-primary)}.post-content-main .alert-toggle{font-size:1.5rem;font-weight:300;color:var(--text-secondary);line-height:1;user-select:none;transition:transform var(--transition-fast)}.post-content-main .alert-collapsible[data-collapsed=true] .alert-toggle{transform:rotate(0)}.post-content-main .alert-collapsible[data-collapsed=false] .alert-toggle{transform:rotate(0)}.post-content-main .alert-content{padding:var(--spacing-md)var(--spacing-lg);color:var(--text-primary);line-height:1.8}.post-content-main .alert-content p{margin-bottom:var(--spacing-sm)}.post-content-main .alert-content p:last-child{margin-bottom:0}.post-content-main .alert-content ul,.post-content-main .alert-content ol{margin:var(--spacing-sm)0;padding-left:1.5em}.post-content-main .alert-content li{margin-bottom:.25em}.post-content-main .alert-content code{background-color:var(--bg-tertiary);padding:.125rem .375rem;border-radius:.25rem;font-size:.9em}.post-content-main .alert-content a{color:var(--accent-primary);text-decoration:underline}.post-content-main .alert-content a:hover{color:var(--accent-hover)}.post-content-main .alert-note{border-left:4px solid #3b82f6}.post-content-main .alert-note .alert-header{background-color:rgba(59,130,246,.1)}[data-theme=light] .post-content-main .alert-note .alert-header{background-color:rgba(59,130,246,.15)}.post-content-main .alert-tip{border-left:4px solid #10b981}.post-content-main .alert-tip .alert-header{background-color:rgba(16,185,129,.1)}[data-theme=light] .post-content-main .alert-tip .alert-header{background-color:rgba(16,185,129,.15)}.post-content-main .alert-important{border-left:4px solid #f59e0b}.post-content-main .alert-important .alert-header{background-color:rgba(245,158,11,.1)}[data-theme=light] .post-content-main .alert-important .alert-header{background-color:rgba(245,158,11,.15)}.post-content-main .alert-warning{border-left:4px solid #f59e0b}.post-content-main .alert-warning .alert-header{background-color:rgba(245,158,11,.1)}[data-theme=light] .post-content-main .alert-warning .alert-header{background-color:rgba(245,158,11,.15)}.post-content-main .alert-caution{border-left:4px solid #ef4444}.post-content-main .alert-caution .alert-header{background-color:rgba(239,68,68,.1)}[data-theme=light] .post-content-main .alert-caution .alert-header{background-color:rgba(239,68,68,.15)}.post-content-main .alert-info{border-left:4px solid #3b82f6}.post-content-main .alert-info .alert-header{background-color:rgba(59,130,246,.1)}[data-theme=light] .post-content-main .alert-info .alert-header{background-color:rgba(59,130,246,.15)}.post-content-main .alert-success{border-left:4px solid #10b981}.post-content-main .alert-success .alert-header{background-color:rgba(16,185,129,.1)}[data-theme=light] .post-content-main .alert-success .alert-header{background-color:rgba(16,185,129,.15)}.post-content-main .alert-question{border-left:4px solid #8b5cf6}.post-content-main .alert-question .alert-header{background-color:rgba(139,92,246,.1)}[data-theme=light] .post-content-main .alert-question .alert-header{background-color:rgba(139,92,246,.15)}.post-content-main .alert-danger{border-left:4px solid #ef4444}.post-content-main .alert-danger .alert-header{background-color:rgba(239,68,68,.1)}[data-theme=light] .post-content-main .alert-danger .alert-header{background-color:rgba(239,68,68,.15)}.post-content-main .alert-bug{border-left:4px solid #f97316}.post-content-main .alert-bug .alert-header{background-color:rgba(249,115,22,.1)}[data-theme=light] .post-content-main .alert-bug .alert-header{background-color:rgba(249,115,22,.15)}.post-content-main .alert-example{border-left:4px solid #6366f1}.post-content-main .alert-example .alert-header{background-color:rgba(99,102,241,.1)}[data-theme=light] .post-content-main .alert-example .alert-header{background-color:rgba(99,102,241,.15)}[data-theme=light] .post-content-main blockquote.blockquote-regular{background-color:var(--glass-block);border-left-color:var(--accent-primary)}[data-theme=light] .post-content-main .alert{background-color:var(--glass-block)}[data-theme=light] .post-content-main .alert-content code{background-color:rgba(107,33,168,.1)}@media(max-width:768px){.post-content-main blockquote.blockquote-regular{margin:var(--spacing-sm)0;padding:var(--spacing-sm)var(--spacing-md)}.post-content-main .alert{margin:var(--spacing-sm)0}.post-content-main .alert-header{padding:var(--spacing-sm)var(--spacing-md);font-size:.9rem}.post-content-main .alert-content{padding:var(--spacing-sm)var(--spacing-md)}.post-content-main .alert-icon{font-size:1.1rem}}.pagination{margin:var(--spacing-2xl)0;padding:var(--spacing-lg);display:flex;flex-direction:column;gap:var(--spacing-md);align-items:center;background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color)}.pagination-info{display:flex;flex-direction:column;align-items:center;gap:var(--spacing-xs);text-align:center}.pagination-text{font-size:.875rem;color:var(--text-secondary);font-weight:500}.pagination-current{font-weight:700;color:var(--accent-primary)}.pagination-total{font-weight:600;color:var(--text-primary)}.pagination-count{font-size:.75rem;color:var(--text-muted)}.pagination-controls{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--spacing-sm)}.pagination-link{display:flex;align-items:center;gap:var(--spacing-xs);padding:var(--spacing-sm)var(--spacing-md);background-color:var(--bg-tertiary);color:var(--text-primary);text-decoration:none;border-radius:.375rem;border:1px solid var(--border-color);font-size:.875rem;font-weight:500;transition:all var(--transition-fast);cursor:pointer;min-height:2.5rem;min-width:2.5rem;justify-content:center}.pagination-link:hover:not(.pagination-link-disabled){background-color:var(--accent-primary);color:var(--bg-primary);border-color:var(--accent-primary);box-shadow:var(--shadow-sm);transform:translateY(-1px)}.pagination-link:focus-visible{outline:2px solid var(--accent-primary);outline-offset:2px}.pagination-link-disabled{opacity:.4;cursor:not-allowed;background-color:var(--bg-secondary);color:var(--text-muted)}.pagination-icon{width:1rem;height:1rem;flex-shrink:0}.pagination-link-text{white-space:nowrap}@media(max-width:640px){.pagination-link-first .pagination-link-text,.pagination-link-last .pagination-link-text{display:none}.pagination-link-prev .pagination-link-text,.pagination-link-next .pagination-link-text{display:none}}.pagination-numbers{display:flex;align-items:center;gap:var(--spacing-xs);margin:0 var(--spacing-sm)}.pagination-number{display:flex;align-items:center;justify-content:center;min-width:2.5rem;height:2.5rem;padding:0 var(--spacing-sm);background-color:var(--bg-tertiary);color:var(--text-primary);text-decoration:none;border-radius:.375rem;border:1px solid var(--border-color);font-size:.875rem;font-weight:500;transition:all var(--transition-fast);cursor:pointer}.pagination-number:hover{background-color:var(--accent-primary);color:var(--bg-primary);border-color:var(--accent-primary);box-shadow:var(--shadow-sm);transform:translateY(-1px)}.pagination-number:focus-visible{outline:2px solid var(--accent-primary);outline-offset:2px}.pagination-number-current{background-color:var(--accent-primary);color:var(--bg-primary);border-color:var(--accent-primary);font-weight:700;cursor:default;box-shadow:var(--shadow-sm)}.pagination-number-current:hover{transform:none;background-color:var(--accent-primary);color:var(--bg-primary)}.pagination-ellipsis{display:flex;align-items:center;justify-content:center;min-width:2.5rem;height:2.5rem;color:var(--text-muted);font-size:.875rem;user-select:none}[data-theme=light] .pagination{background-color:var(--glass-block)}[data-theme=light] .pagination-link{background-color:rgba(107,33,168,.1)}[data-theme=light] .pagination-link:hover:not(.pagination-link-disabled){background-color:var(--accent-primary);color:#fff}[data-theme=light] .pagination-number{background-color:rgba(107,33,168,.1)}[data-theme=light] .pagination-number:hover{background-color:var(--accent-primary);color:#fff}[data-theme=light] .pagination-number-current{background-color:var(--accent-primary);color:#fff}@media(max-width:768px){.pagination{padding:var(--spacing-md);gap:var(--spacing-sm)}.pagination-controls{gap:var(--spacing-xs)}.pagination-numbers{margin:0 var(--spacing-xs);gap:.25rem}.pagination-number{min-width:2.25rem;height:2.25rem;font-size:.8125rem}.pagination-link{padding:var(--spacing-xs)var(--spacing-sm);min-height:2.25rem;min-width:2.25rem;font-size:.8125rem}.pagination-icon{width:.875rem;height:.875rem}}@media(max-width:480px){.pagination-info{font-size:.8125rem}.pagination-count{font-size:.6875rem}.pagination-numbers{max-width:100%;overflow-x:auto;scrollbar-width:thin;-webkit-overflow-scrolling:touch}.pagination-numbers::-webkit-scrollbar{height:4px}.pagination-numbers::-webkit-scrollbar-track{background:var(--bg-secondary);border-radius:2px}.pagination-numbers::-webkit-scrollbar-thumb{background:var(--accent-primary);border-radius:2px}}.search-modal{position:fixed;inset:0;z-index:1000;display:none;align-items:flex-start;justify-content:center;padding-top:20vh;overflow-y:auto}.search-modal[aria-hidden=false]{display:flex}.search-modal-backdrop{position:absolute;inset:0;background-color:rgba(0,0,0,.6);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);animation:fadeIn var(--transition-base)}.search-modal-container{position:relative;width:90%;max-width:600px;z-index:1001;flex-shrink:0;animation:slideDown var(--transition-base)}.search-input-wrapper{position:relative;display:flex;align-items:center;background:rgba(30,30,45,.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:12px;border:1px solid transparent;box-shadow:0 10px 40px rgba(0,0,0,.4),0 0 10px rgba(160,32,240,.1);overflow:hidden}.search-input-icon{position:absolute;left:var(--spacing-lg);width:1.5rem;height:1.5rem;color:rgba(255,255,255,.5);pointer-events:none;flex-shrink:0;z-index:1}.search-input{flex:1;height:60px;padding:0 calc( var(--spacing-lg) + 2rem + var(--spacing-sm) + 2rem + var(--spacing-lg) )0 calc(var(--spacing-lg) + 1.5rem + var(--spacing-md));background:0 0;border:none;color:rgba(255,255,255,.95);font-family:var(--font-body);font-size:1.2rem;transition:all var(--transition-fast)}.search-input::-webkit-search-cancel-button{display:none;-webkit-appearance:none}.search-input::-ms-clear{display:none}.search-input::placeholder{color:rgba(255,255,255,.4)}.search-input:focus{outline:none}.search-input-clear{position:absolute;right:calc(var(--spacing-lg) + 2rem + var(--spacing-sm));background:0 0;border:none;color:rgba(255,255,255,.5);cursor:pointer;padding:var(--spacing-sm);display:none;align-items:center;justify-content:center;transition:color var(--transition-fast);z-index:2;width:2rem;height:2rem;border-radius:.25rem}.search-input-clear.visible{display:flex}.search-input-clear:hover{color:var(--accent-primary)}.search-input-clear svg{width:1.25rem;height:1.25rem}.search-modal-close{position:absolute;right:var(--spacing-lg);background:0 0;border:none;color:rgba(255,255,255,.5);cursor:pointer;padding:var(--spacing-sm);display:flex;align-items:center;justify-content:center;transition:color var(--transition-fast);z-index:2;width:2rem;height:2rem;border-radius:.25rem}.search-modal-close:hover{color:var(--accent-primary)}.search-modal-close svg{width:1.25rem;height:1.25rem}.search-results{margin-top:.5rem;max-height:65vh;overflow-y:auto;background:rgba(30,30,45,.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:12px;border:1px solid transparent;box-shadow:0 10px 40px rgba(0,0,0,.4),0 0 10px rgba(160,32,240,.1);display:none}.search-results:not(:empty){display:block}.search-result-item{display:block;padding:var(--spacing-md)var(--spacing-lg);border-bottom:1px solid rgba(255,255,255,.15);text-decoration:none;transition:background-color var(--transition-fast)}.search-result-item:last-child{border-bottom:none}.search-result-item:hover{background-color:rgba(255,255,255,5%)}.search-result-title{font-weight:700;color:#fff;margin-bottom:.25rem;font-size:1rem;line-height:1.4}.search-result-title mark{background-color:rgba(160,32,240,.3);color:#fff;padding:.125rem 0;border-radius:.125rem;font-weight:700;display:inline-block;vertical-align:baseline}.search-result-summary{color:rgba(255,255,255,.6);font-size:.875rem;margin-bottom:.25rem;line-height:1.5}.search-result-summary mark{background-color:rgba(160,32,240,.2);color:rgba(255,255,255,.9);padding:.125rem 0;border-radius:.125rem;display:inline-block}.search-result-date{display:flex;align-items:center;gap:.375rem;color:rgba(255,255,255,.4);font-size:.75rem;margin-top:.25rem}.search-result-date-icon{width:.875rem;height:.875rem}.search-result-empty{padding:var(--spacing-xl);text-align:center;color:rgba(255,255,255,.5)}@media(max-width:768px){.search-modal{padding-top:8vh}.search-results{max-height:60vh}}@keyframes fadeIn{from{opacity:0}to{opacity:1}}@keyframes slideDown{from{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translateY(0)}}@media(max-width:768px){.hero-content{grid-template-columns:1fr;gap:var(--spacing-xl);text-align:center}.hero-image{order:-1}.hero-logo,.hero-logo-placeholder{max-width:150px}.hero-title-line1,.hero-title-line2{font-size:clamp(2rem,8vw,3rem)}}@media(max-width:1024px){.single-post-wrapper{flex-direction:column}.post-toc{position:sticky;top:70px;width:100%;margin-bottom:var(--spacing-lg);order:-1}.toc-content.expanded{max-height:300px}}@media(max-width:1024px){.header-logo .logo-link{font-size:1.125rem}}@media(max-width:768px){.search-modal-container{width:90%}.header-container{padding:0 var(--spacing-sm)}.header-content{gap:var(--spacing-xs)}.header-logo{flex:auto;min-width:0}.header-logo .logo-link{font-size:.875rem;max-width:100%}.header-menu{gap:var(--spacing-xs);flex-shrink:0}.menu-list{gap:var(--spacing-xs)}.menu-link{padding:var(--spacing-xs)var(--spacing-sm);font-size:.875rem;white-space:nowrap}.search-toggle,.theme-toggle{padding:var(--spacing-xs);width:1.5rem;height:1.5rem;flex-shrink:0}.search-icon{width:var(--icon-sm);height:var(--icon-sm)}.theme-toggle .icon-sun,.theme-toggle .icon-moon{width:1rem;height:1rem}main{padding:var(--spacing-lg)var(--spacing-md)}.post-card{flex-direction:column}.post-image{width:100%;height:200px}.post-title-main{font-size:2rem}.year-posts{margin-left:0}.post-content-main pre,.post-content-main .highlight,.post-content-main pre.highlight{max-width:100%;width:100%;box-sizing:border-box;overflow-x:auto;-webkit-overflow-scrolling:touch}.post-content-main pre code{min-width:0}.single-post{width:100%;max-width:100%;box-sizing:border-box}.post-content-main{width:100%;max-width:100%;box-sizing:border-box;overflow-x:hidden}.code-block-copy{right:calc(.5rem + 85px + .5rem);padding:.375rem .5rem;font-size:.7rem;width:auto;min-width:32px;box-sizing:border-box;margin-right:0}.code-block-copy .copy-text,.code-block-copy .copied-text{display:none}.code-block-toggle{padding:.375rem .5rem;font-size:.7rem;width:85px;box-sizing:border-box}}@media(max-width:480px){.header-container{padding:0 var(--spacing-xs)}.header-content{gap:.25rem}.header-logo{flex:auto;min-width:0}.header-logo .logo-link{font-size:.75rem;max-width:100%}.header-menu{gap:.25rem}.menu-list{gap:.25rem}.menu-link{padding:var(--spacing-xs);font-size:.75rem}.about-content{gap:var(--spacing-sm)}.about-avatar{width:60px;height:60px}.about-name{font-size:1.25rem}.post-title-main{font-size:1.75rem}.tags-list{gap:var(--spacing-sm)}.tag-chip-large{padding:var(--spacing-xs)var(--spacing-sm);font-size:.875rem}.code-block-copy{right:calc(1rem + 85px)}} \ No newline at end of file diff --git a/public/css/bundle.min.948f22fa5116b6eb619af6ddf3f031cb443efd254975d5d5c20832145ef0c86c.css b/public/css/bundle.min.948f22fa5116b6eb619af6ddf3f031cb443efd254975d5d5c20832145ef0c86c.css new file mode 100755 index 0000000..1e533fa --- /dev/null +++ b/public/css/bundle.min.948f22fa5116b6eb619af6ddf3f031cb443efd254975d5d5c20832145ef0c86c.css @@ -0,0 +1 @@ +.skip-to-main{position:absolute;top:-100px;left:0;background:var(--accent-primary);color:var(--bg-deep);padding:var(--spacing-sm)var(--spacing-md);text-decoration:none;font-weight:600;z-index:1000;border-radius:0 0 .25rem 0;transition:top .2s ease;clip:rect(0,0,0,0);clip-path:inset(50%);width:1px;height:1px;overflow:hidden}.skip-to-main:focus{top:0;clip:auto;clip-path:none;width:auto;height:auto;overflow:visible}body{overflow-x:hidden;position:relative}code,pre{font-family:var(--font-mono)}.glass-effect{background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--border-color);border-radius:.5rem}.card-base{background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color);transition:all var(--transition-base)}.card-base:hover{box-shadow:var(--shadow-md);border-color:var(--accent-primary)}.icon-sm{width:var(--icon-sm);height:var(--icon-sm)}.icon-md{width:var(--icon-md);height:var(--icon-md)}.icon-lg{width:var(--icon-lg);height:var(--icon-lg)}body{overflow-x:hidden}code,pre{font-family:var(--font-mono)}.glass-effect{background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--border-color);border-radius:.5rem}.card-base{background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color);transition:all var(--transition-base)}.card-base:hover{box-shadow:var(--shadow-md);border-color:var(--accent-primary)}.icon-sm{width:var(--icon-sm);height:var(--icon-sm)}.icon-md{width:var(--icon-md);height:var(--icon-md)}.icon-lg{width:var(--icon-lg);height:var(--icon-lg)}main{max-width:1200px;margin:0 auto;padding:var(--spacing-xl)var(--spacing-lg);min-height:calc(100vh - 200px)}.sticky-header{position:sticky;top:0;z-index:100;background-color:var(--bg-primary);border-bottom:1px solid var(--border-color);backdrop-filter:blur(10px);background-color:rgba(15,23,42,.8);transition:all var(--transition-base)}[data-theme=light] .sticky-header{background-color:rgba(255,255,255,.8)}.header-container{max-width:1200px;margin:0 auto;padding:0 var(--spacing-lg);box-sizing:border-box}.header-nav{display:flex;align-items:center;justify-content:space-between;padding:var(--spacing-md)0}.header-content{display:flex;align-items:center;justify-content:space-between;width:100%;min-width:0;gap:var(--spacing-sm);box-sizing:border-box}.header-logo{flex-shrink:1;min-width:0;overflow:hidden}.header-logo .logo-link{font-family:var(--font-logo);font-size:1.25rem;font-weight:700;color:var(--text-primary);text-decoration:none;transition:color var(--transition-fast);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block}.header-logo .logo-link:hover{color:var(--accent-primary)}.header-menu{display:flex;align-items:center;gap:var(--spacing-md);flex-shrink:0}.menu-list{display:flex;list-style:none;margin:0;padding:0;gap:var(--spacing-md)}.menu-link{color:var(--text-secondary);text-decoration:none;font-weight:500;padding:var(--spacing-sm)var(--spacing-md);border-radius:.375rem;transition:all var(--transition-fast)}.menu-link:hover{color:var(--accent-primary);background-color:var(--bg-secondary)}.menu-link.active{color:var(--accent-light-purple);background-color:var(--bg-secondary)}.search-toggle{background:0 0;border:none;color:var(--text-secondary);cursor:pointer;padding:var(--spacing-sm);display:flex;align-items:center;transition:color var(--transition-fast)}.search-toggle:hover{color:var(--accent-primary)}.search-icon{width:var(--icon-md);height:var(--icon-md)}.theme-toggle{background:0 0;border:none;color:var(--text-secondary);cursor:pointer;padding:var(--spacing-sm);display:flex;align-items:center;transition:color var(--transition-fast)}.theme-toggle:hover{color:var(--accent-primary)}.theme-toggle .icon-sun,.theme-toggle .icon-moon{width:1.25rem;height:1.25rem}[data-theme=light] .icon-sun{display:none}[data-theme=light] .icon-moon{display:block}.icon-sun{display:block}.icon-moon{display:none}.hero-section{width:100%;padding:var(--spacing-2xl)var(--spacing-lg);margin-bottom:var(--spacing-2xl)}.hero-container{max-width:1200px;margin:0 auto}.hero-content{display:grid;grid-template-columns:1fr 1fr;gap:var(--spacing-2xl);align-items:center}.hero-text{display:flex;flex-direction:column;gap:var(--spacing-md)}.hero-title{display:flex;flex-direction:column;gap:0;margin:0;line-height:1.1}.hero-title-line1{font-family:var(--font-heading);font-size:clamp(2.5rem,5vw,4.5rem);font-weight:900;color:var(--text-primary);letter-spacing:-.02em}.hero-title-line2{font-family:var(--font-heading);font-size:clamp(2.5rem,5vw,4.5rem);font-weight:900;color:transparent;-webkit-text-stroke:2px var(--accent-light-purple);text-stroke:2px var(--accent-light-purple);letter-spacing:-.02em}.hero-subtitle{font-family:var(--font-mono);font-size:clamp(.875rem,1.5vw,1.125rem);color:var(--text-secondary);margin:var(--spacing-md)0 0;opacity:.8}.hero-image{display:flex;justify-content:center;align-items:center}.hero-logo{max-width:100%;height:auto;filter:drop-shadow(0 0 10px var(--accent-primary-rgba-03));animation:float 3s ease-in-out infinite}.hero-logo-placeholder{width:200px;height:200px;background:var(--glass-surface);border-radius:50%;border:2px solid var(--glass-border);filter:drop-shadow(0 0 20px var(--accent-primary-rgba-05));animation:float 3s ease-in-out infinite}@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-20px)}}.home-container{max-width:900px;margin:0 auto}.posts-section{margin-top:var(--spacing-2xl)}.posts-title{font-size:1.5rem;font-weight:700;margin-bottom:var(--spacing-lg);color:var(--text-primary)}.posts-list{display:flex;flex-direction:column;gap:var(--spacing-xl)}.post-card{display:flex;gap:var(--spacing-lg);padding:var(--spacing-lg);cursor:pointer;position:relative;background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color);transition:all var(--transition-base)}.post-card:hover{box-shadow:var(--shadow-md);border-color:var(--accent-primary)}.post-image{flex-shrink:0;width:200px;height:150px;overflow:hidden;border-radius:.375rem}.post-image img{width:100%;height:100%;object-fit:cover}.post-content{flex:1;display:flex;flex-direction:column;gap:var(--spacing-sm)}.post-title{margin:0;font-size:1.5rem;font-weight:700}.post-title a{color:var(--text-primary);text-decoration:underline;text-decoration-color:transparent;text-underline-offset:.25em;transition:text-decoration-color var(--transition-base),color var(--transition-base)}.post-card:hover .post-title a{text-decoration-color:var(--accent-primary)}.post-description{color:var(--text-secondary);margin:0;line-height:1.6}.post-meta-inline{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing-sm);font-size:.875rem;color:var(--text-muted)}.post-meta-inline .post-date,.post-meta-inline .post-reading-time,.post-meta-inline .post-word-count{display:flex;align-items:center;gap:.375rem}.post-meta-inline .meta-icon{width:var(--icon-sm);height:var(--icon-sm);flex-shrink:0}.post-meta-inline .meta-separator{color:var(--text-muted);opacity:.5}.post-tags{display:flex;flex-wrap:wrap;gap:var(--spacing-sm);margin-top:var(--spacing-sm)}.post-tag{padding:.25rem .625rem;background-color:var(--tag-bg);color:var(--tag-color);border:none;border-radius:.25rem;font-size:.875rem;font-weight:400;font-family:var(--font-mono);text-decoration:none;transition:all var(--transition-fast);position:relative;z-index:10;cursor:pointer;display:inline-block}.post-tag:hover{background-color:var(--accent-light-purple-rgba-01);color:var(--tag-color);box-shadow:0 0 8px var(--accent-light-purple-rgba-03)}.single-post-wrapper{display:flex;gap:var(--spacing-xl);max-width:1200px;margin:0 auto;align-items:flex-start}.single-post{flex:1;min-width:0;max-width:800px;overflow-wrap:break-word;word-wrap:break-word}.post-toc{position:sticky;top:calc(var(--spacing-lg) + 60px);width:260px;flex-shrink:0;padding:0;background-color:var(--bg-deep);background-image:radial-gradient( circle at center,rgba(30,58,138,.3) 0%,rgba(15,23,42,.5) 40%,var(--bg-deep) 100% );background-attachment:fixed;background-size:100% 100%;background-position:50%;border:1px solid var(--border-color);border-radius:.5rem;overflow:hidden;z-index:50}.post-toc::before{content:"";position:absolute;inset:0;background-color:var(--bg-secondary);border-radius:.5rem;pointer-events:none;z-index:1}[data-theme=light] .post-toc{background-image:radial-gradient( circle at center,rgba(196,133,246,.2) 0%,rgba(243,232,255,.4) 40%,#ffffff 100% )}.toc-toggle{width:100%;display:flex;align-items:center;gap:var(--spacing-sm);padding:var(--spacing-md);background-color:transparent;border:none;color:var(--text-primary);font-size:.875rem;font-weight:600;cursor:pointer;transition:all var(--transition-base);text-align:left}.toc-toggle:hover{background-color:var(--accent-mid-purple-rgba-05)}.toc-burger-icon{width:var(--icon-md);height:var(--icon-md);flex-shrink:0;color:var(--accent-primary)}.toc-toggle-text{flex:1;text-transform:uppercase;letter-spacing:.05em;font-size:.75rem;color:var(--text-muted)}.toc-chevron-icon{width:var(--icon-sm);height:var(--icon-sm);flex-shrink:0;color:var(--text-muted);transition:transform var(--transition-base)}.toc-toggle[aria-expanded=true] .toc-chevron-icon{transform:rotate(180deg)}.toc-content{max-height:0;overflow:hidden;transition:max-height .3s cubic-bezier(.4,0,.2,1)}.toc-content.expanded{max-height:calc(100vh - 200px);overflow-y:auto}.toc-content::-webkit-scrollbar{width:4px}.toc-content::-webkit-scrollbar-track{background:0 0}.toc-content::-webkit-scrollbar-thumb{background-color:var(--border-color);border-radius:2px}.toc-content::-webkit-scrollbar-thumb:hover{background-color:var(--text-muted)}.toc-nav{font-size:.8125rem;line-height:1.8;padding:0 var(--spacing-md)var(--spacing-md)}.toc-nav ul{list-style:none;padding-left:0;margin:0}.toc-nav li{margin-bottom:.375rem}.toc-nav a{color:var(--text-secondary);text-decoration:none;display:block;padding:.25rem .5rem;transition:all var(--transition-fast);border-left:2px solid transparent;border-radius:0 .25rem .25rem 0}.toc-nav a:hover{color:var(--text-primary);background-color:var(--accent-mid-purple-rgba-05)}.toc-nav a.active{color:var(--accent-primary);border-left-color:var(--accent-primary);background-color:var(--accent-mid-purple-rgba-01)}.toc-nav ul ul{margin-top:.125rem;padding-left:var(--spacing-sm);margin-left:.5rem;border-left:1px solid var(--border-color)}.toc-nav ul ul a{font-size:.75rem;padding-left:.5rem;color:var(--text-muted)}.toc-nav ul ul a:hover{color:var(--text-secondary)}.toc-nav ul ul a.active{color:var(--accent-primary)}.post-image-single{width:100%;max-width:100%;margin-bottom:var(--spacing-xl);border-radius:.5rem;overflow:hidden;border:1px solid var(--border-color);box-shadow:var(--shadow-md);background-color:var(--bg-secondary);padding:.25rem}.post-image-single img{width:100%;height:auto;display:block;object-fit:cover;border-radius:.375rem}.post-header{margin-bottom:var(--spacing-xl);padding-bottom:var(--spacing-lg);border-bottom:1px solid var(--border-color)}.post-title-main{font-size:2.5rem;font-weight:700;margin:0 0 var(--spacing-md);color:var(--text-primary);line-height:1.2}.post-description-main{font-size:1.25rem;color:var(--text-secondary);margin:0 0 var(--spacing-lg)}.post-meta{display:flex;flex-direction:column;gap:var(--spacing-md)}.post-meta-info{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing-sm);font-size:.875rem;color:var(--text-muted)}.post-date,.post-reading-time,.post-word-count{display:flex;align-items:center;gap:.375rem}.meta-icon{width:var(--icon-sm);height:var(--icon-sm);flex-shrink:0}.meta-separator{color:var(--text-muted);opacity:.5}.post-content-main{font-size:1.125rem;line-height:1.8;color:var(--text-primary);overflow-wrap:break-word;word-wrap:break-word;max-width:100%;width:100%;box-sizing:border-box}.post-content-main h2{font-size:1.75rem;margin-top:var(--spacing-xl);margin-bottom:var(--spacing-md);color:var(--text-primary)}.post-content-main h3{font-size:1.5rem;margin-top:var(--spacing-lg);margin-bottom:var(--spacing-sm);color:var(--text-primary)}.post-content-main p{margin-bottom:var(--spacing-md)}.post-content-main ul,.post-content-main ol{margin-bottom:var(--spacing-md);padding-left:1.5em;margin-left:0}.post-content-main li{margin-bottom:.5em;line-height:1.8}.post-content-main ul ul,.post-content-main ol ol,.post-content-main ul ol,.post-content-main ol ul{margin-top:.5em;margin-bottom:.5em;padding-left:1.5em}.post-content-main a{color:var(--accent-primary);text-decoration:underline;transition:color var(--transition-fast)}.post-content-main a:hover{color:var(--accent-hover)}.post-content-main pre{font-family:var(--font-mono);overflow-x:auto;max-width:100%;word-break:normal;box-sizing:border-box;width:100%}.post-content-main code{font-family:var(--font-mono);word-wrap:break-word;overflow-wrap:break-word}.post-content-main pre code{white-space:pre;word-break:normal;overflow-wrap:normal;display:block}.post-content-main .highlight,.post-content-main pre.highlight{max-width:100%;overflow-x:auto;box-sizing:border-box;width:100%}.post-content-main .highlight pre{margin:0;padding:inherit}.post-footer{margin-top:var(--spacing-xl);padding-top:var(--spacing-lg);border-top:1px solid var(--border-color)}.post-tags-footer{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing-sm)}.tags-label{font-weight:600;color:var(--text-secondary)}.post-navigation{display:flex;justify-content:space-between;margin-top:var(--spacing-2xl);padding-top:var(--spacing-xl);border-top:1px solid var(--border-color);gap:var(--spacing-lg)}.nav-link{flex:1;padding:var(--spacing-md);background-color:var(--bg-secondary);border-radius:.375rem;text-decoration:none;transition:all var(--transition-fast)}.nav-link:hover{background-color:var(--bg-tertiary);transform:translateX(-2px)}.nav-next:hover{transform:translateX(2px)}.nav-label{display:block;font-size:.875rem;color:var(--text-muted);margin-bottom:var(--spacing-xs)}.nav-title{display:block;color:var(--text-primary);font-weight:600}.list-container{max-width:900px;margin:0 auto}.list-header{margin-bottom:var(--spacing-xl)}.list-title{display:flex;flex-direction:column;align-items:center;gap:0;margin:0 0 var(--spacing-sm);line-height:1.1;text-align:center}.list-title-line1{font-family:var(--font-heading);font-size:clamp(2.5rem,5vw,4.5rem);font-weight:900;color:var(--text-primary);letter-spacing:-.02em}.list-title-line2{font-family:var(--font-heading);font-size:clamp(2.5rem,5vw,4.5rem);font-weight:900;color:transparent;-webkit-text-stroke:2px var(--accent-light-purple);text-stroke:2px var(--accent-light-purple);letter-spacing:-.02em}.list-description{color:var(--text-secondary);margin:0}.list-count{color:var(--text-muted);font-size:.875rem;margin-top:var(--spacing-sm)}.list-count-pages{color:var(--text-secondary);font-weight:500;margin-left:var(--spacing-xs)}.filter-accordion{margin-bottom:var(--spacing-xl);overflow:hidden;background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color)}.filter-toggle-btn{width:100%;display:flex;align-items:center;justify-content:space-between;padding:var(--spacing-md)var(--spacing-lg);background-color:transparent;border:none;color:var(--text-primary);font-size:.875rem;font-weight:600;cursor:pointer;transition:all var(--transition-base)}.filter-toggle-btn:hover{background-color:var(--accent-mid-purple-rgba-05)}.filter-toggle-icon{width:var(--icon-md);height:var(--icon-md);transition:transform var(--transition-base)}.filter-toggle-btn.expanded .filter-toggle-icon{transform:rotate(180deg)}.posts-filter{display:flex;flex-wrap:wrap;gap:var(--spacing-lg);align-items:flex-start;padding:0 var(--spacing-lg);max-height:0;overflow:hidden;opacity:0;transition:max-height .4s cubic-bezier(.4,0,.2,1),padding .4s cubic-bezier(.4,0,.2,1),opacity .3s ease-out}.posts-filter.expanded{max-height:300px;padding-top:var(--spacing-lg);padding-bottom:var(--spacing-lg);opacity:1}.filter-group{display:flex;flex-direction:column;gap:var(--spacing-xs);flex:1;min-width:200px}.filter-group-tags{flex:1 1 100%;min-width:100%;display:flex;flex-direction:column}.filter-label{font-size:.875rem;font-weight:600;color:var(--text-secondary);margin-bottom:var(--spacing-xs)}.tag-search-container{position:relative;margin-bottom:var(--spacing-md)}.tag-search-input{width:100%;padding:var(--spacing-sm)var(--spacing-md);background-color:var(--bg-primary);border:1px solid var(--border-color);border-radius:.375rem;color:var(--text-primary);font-size:.875rem;transition:all var(--transition-base)}.tag-search-input:focus{outline:none;border-color:var(--accent-primary);box-shadow:0 0 0 3px var(--accent-mid-purple-rgba-01)}.tag-search-results{position:absolute;top:100%;left:0;right:0;margin-top:var(--spacing-xs);background-color:var(--bg-primary);border:1px solid var(--border-color);border-radius:.375rem;box-shadow:var(--shadow-lg);max-height:200px;overflow-y:auto;z-index:10;display:none}.tag-search-result-item{padding:var(--spacing-sm)var(--spacing-md);cursor:pointer;color:var(--text-primary);transition:all var(--transition-base);border-bottom:1px solid var(--border-color)}.tag-search-result-item:last-child{border-bottom:none}.tag-search-result-item:hover,.tag-search-result-item.selected{background-color:var(--bg-secondary);color:var(--accent-primary)}.tag-search-no-results{padding:var(--spacing-sm)var(--spacing-md);color:var(--text-muted);font-size:.875rem;text-align:center}.filter-select{padding:var(--spacing-sm)2.5rem var(--spacing-sm)var(--spacing-md);background-color:var(--bg-primary);border:1px solid var(--border-color);border-radius:.375rem;color:var(--text-primary);font-size:.875rem;cursor:pointer;transition:all var(--transition-base);appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23a8a8a8'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right .75rem center;background-size:1rem}.filter-select:hover{border-color:var(--accent-primary)}.filter-select:focus{outline:none;border-color:var(--accent-primary);box-shadow:0 0 0 3px var(--accent-mid-purple-rgba-01)}.filter-tags{display:flex;flex-wrap:wrap;gap:var(--spacing-sm);margin-top:var(--spacing-xs);min-height:40px;max-height:40px;overflow-y:auto;align-content:flex-start}.filter-tag-checkbox{display:flex;align-items:center;cursor:pointer;user-select:none}.filter-tag-input{display:none}.filter-tag-label{padding:var(--spacing-xs)var(--spacing-sm);background-color:var(--bg-primary);border:1px solid var(--border-color);border-radius:.375rem;font-size:.875rem;color:var(--text-primary);transition:all var(--transition-base);cursor:pointer}.filter-tag-checkbox:hover .filter-tag-label{border-color:var(--accent-primary);background-color:var(--accent-mid-purple-rgba-05)}.filter-tag-input:checked+.filter-tag-label{background-color:var(--accent-primary);border-color:var(--accent-primary);color:#fff}.filter-clear-btn{padding:var(--spacing-sm)var(--spacing-md);background-color:transparent;border:1px solid var(--border-color);border-radius:.375rem;color:var(--text-secondary);font-size:.875rem;cursor:pointer;transition:all var(--transition-base);align-self:flex-end;margin-top:calc(var(--spacing-xs) + 1.25rem)}.filter-clear-btn:hover{border-color:var(--accent-primary);color:var(--accent-primary);background-color:var(--accent-mid-purple-rgba-05)}.pagination{display:flex;justify-content:space-between;align-items:center;margin-top:var(--spacing-2xl);padding-top:var(--spacing-xl);border-top:1px solid var(--border-color)}.pagination-link{padding:var(--spacing-sm)var(--spacing-md);background-color:var(--bg-secondary);color:var(--text-primary);text-decoration:none;border-radius:.375rem;transition:all var(--transition-fast)}.pagination-link:hover{background-color:var(--accent-primary);color:#fff}.pagination-info{color:var(--text-muted);font-size:.875rem}.tags-page{max-width:900px;margin:0 auto;text-align:center}.tags-title{display:flex;flex-direction:column;align-items:center;gap:0;margin-bottom:var(--spacing-lg);line-height:1.1;text-align:center}.tags-help{margin-bottom:var(--spacing-xl);color:var(--text-muted);font-size:.875rem}.tags-cloud{padding:var(--spacing-xl);background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color)}.tags-list{display:flex;flex-wrap:wrap;gap:var(--spacing-md);justify-content:center}.tag-item{text-decoration:none}.tag-chip-large{display:flex;align-items:center;padding:.25rem .625rem;background-color:var(--tag-bg);color:var(--tag-color);border:none;border-radius:.25rem;font-size:.875rem;font-weight:400;font-family:var(--font-mono);transition:all var(--transition-fast);position:relative;z-index:10;cursor:pointer}.tag-item:hover .tag-chip-large{background-color:var(--accent-light-purple-rgba-01);color:var(--tag-color);box-shadow:0 0 8px var(--accent-light-purple-rgba-03)}.tag-name{font-weight:400;margin-right:0}.tag-count-badge{padding:.125rem .5rem;background-color:transparent;color:var(--text-secondary);border-radius:.25rem;font-size:.75rem;margin-left:.5rem;opacity:.7}.tag-item:hover .tag-count-badge{background-color:transparent;color:var(--text-secondary);opacity:.9}.tags-back{margin-top:var(--spacing-2xl)}.tags-back-link{display:inline-flex;align-items:center;gap:var(--spacing-sm);color:var(--text-secondary);text-decoration:none;transition:color var(--transition-fast)}.tags-back-link:hover{color:var(--accent-primary)}.back-icon{width:var(--icon-sm);height:var(--icon-sm)}.tag-page{max-width:900px;margin:0 auto}.tag-header{text-align:center;margin-bottom:var(--spacing-2xl)}.tag-header-content{display:flex;align-items:center;justify-content:center;gap:var(--spacing-sm);margin-bottom:var(--spacing-sm)}.tag-icon{width:1.25rem;height:1.25rem;color:var(--accent-primary)}.tag-title{font-size:2rem;font-weight:700;margin:0;color:var(--text-primary)}.tag-count{color:var(--text-muted);font-size:.875rem}.tag-posts-grouped{display:flex;flex-direction:column;gap:var(--spacing-2xl)}.year-group{position:relative}.year-indicator{display:flex;align-items:center;margin-bottom:var(--spacing-lg)}.year-circle{position:relative;width:4rem;height:4rem;border-radius:50%;background:linear-gradient( 135deg,var(--accent-primary),var(--accent-light-purple) );display:flex;align-items:center;justify-content:center;font-weight:700;font-size:1.25rem;color:#fff;box-shadow:var(--shadow-md)}.year-glow{position:absolute;inset:0;border-radius:50%;background-color:var(--accent-primary);opacity:.2;filter:blur(8px)}.year-text{position:relative;z-index:1}.year-count{font-size:.875rem;vertical-align:super;margin-left:.125rem;opacity:.9}.year-line{flex:1;height:2px;margin-left:var(--spacing-md);background:linear-gradient(to right,var(--accent-primary),transparent);position:relative;display:flex;align-items:center}.year-count-inline{position:absolute;left:20%;background-color:var(--bg-primary);padding:0 var(--spacing-sm);font-size:.75rem;color:var(--text-muted);font-weight:500;white-space:nowrap;transform:translateY(-50%);top:50%}.year-posts{margin-left:calc(4rem + var(--spacing-md));display:flex;flex-direction:column;gap:var(--spacing-xl)}.year-months{margin-left:0;margin-top:var(--spacing-md);display:flex;flex-direction:column;gap:var(--spacing-lg)}.month-group{display:flex;flex-direction:column;gap:var(--spacing-md)}.month-indicator{display:flex;align-items:center;margin-bottom:var(--spacing-md)}.month-text{font-size:1rem;font-weight:600;color:var(--text-primary);white-space:nowrap;margin-right:var(--spacing-sm)}.month-line{flex:1;height:1px;background:linear-gradient(to right,var(--accent-primary),transparent);position:relative;display:flex;align-items:center;opacity:.7}.month-count-inline{position:absolute;left:20%;background-color:var(--bg-primary);padding:0 var(--spacing-sm);font-size:.75rem;color:var(--text-muted);font-weight:500;white-space:nowrap;transform:translateY(-50%);top:50%}.month-posts{display:flex;flex-direction:column;gap:var(--spacing-xl)}.tag-post-card{position:relative;padding:var(--spacing-lg);background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color);transition:all var(--transition-base)}.tag-post-card:hover{box-shadow:var(--shadow-md);border-color:var(--accent-primary)}.tag-post-link{position:absolute;inset:0;z-index:10}.tag-post-header{display:flex;flex-direction:column;gap:var(--spacing-xs);margin-bottom:var(--spacing-sm)}.tag-post-title{font-size:1.25rem;font-weight:600;margin:0;color:var(--text-primary);text-decoration:underline;text-decoration-color:transparent;text-underline-offset:.25em;transition:text-decoration-color var(--transition-base),color var(--transition-base)}.tag-post-card:hover .tag-post-title{text-decoration-color:var(--accent-primary)}.tag-post-date{font-size:.875rem;color:var(--text-muted)}.tag-post-summary{color:var(--text-secondary);font-size:.875rem;margin-bottom:var(--spacing-sm);line-height:1.6}.tag-post-tags{display:flex;flex-wrap:wrap;gap:var(--spacing-xs);margin-bottom:var(--spacing-sm);position:relative;z-index:20}.tag-chip{padding:.25rem .625rem;background-color:var(--tag-bg);color:var(--tag-color);border:none;border-radius:.25rem;font-size:.875rem;font-weight:400;font-family:var(--font-mono);text-decoration:none;transition:all var(--transition-fast);position:relative;z-index:10;cursor:pointer;display:inline-block}.tag-chip:hover{background-color:var(--accent-light-purple-rgba-01);color:var(--tag-color);box-shadow:0 0 8px var(--accent-light-purple-rgba-03)}.tag-chip-active{background-color:var(--accent-light-purple-rgba-01);color:var(--tag-color);box-shadow:0 0 8px var(--accent-light-purple-rgba-03)}.tag-post-read-more{display:flex;align-items:center;gap:var(--spacing-xs);color:var(--accent-primary);font-size:.875rem;font-weight:500;transition:transform var(--transition-fast)}.tag-post-card:hover .tag-post-read-more{transform:translateX(4px)}.read-more-icon{width:var(--icon-sm);height:var(--icon-sm)}.tag-pagination{display:flex;justify-content:space-between;margin-top:var(--spacing-xl)}.pagination-btn{display:flex;align-items:center;gap:var(--spacing-sm);padding:var(--spacing-sm)var(--spacing-md);background-color:var(--bg-secondary);color:var(--text-primary);text-decoration:none;border-radius:9999px;transition:all var(--transition-fast)}.pagination-btn:hover{background-color:var(--accent-primary);color:#fff}.pagination-icon{width:var(--icon-sm);height:var(--icon-sm)}.tag-nav-links{display:flex;justify-content:center;gap:var(--spacing-xl);margin-top:var(--spacing-xl)}.tag-nav-link{color:var(--text-secondary);text-decoration:none;transition:color var(--transition-fast)}.tag-nav-link:hover{color:var(--accent-primary)}.archives-page{max-width:900px;margin:0 auto}.archives-header{margin-bottom:var(--spacing-xl)}.archives-title{font-size:2rem;font-weight:700;margin:0 0 var(--spacing-sm);color:var(--text-primary)}.archives-description{color:var(--text-secondary);margin:0}.archive-year{margin-bottom:var(--spacing-2xl)}.archive-year-title{font-size:1.5rem;font-weight:700;margin-bottom:var(--spacing-lg);color:var(--text-primary)}.archive-month{margin-bottom:var(--spacing-lg);margin-left:var(--spacing-lg)}.archive-month-title{font-size:1.125rem;font-weight:600;margin-bottom:var(--spacing-sm);color:var(--text-secondary)}.archive-posts{list-style:none;padding:0;margin:0}.archive-post-item{display:flex;gap:var(--spacing-md);padding:var(--spacing-sm)0}.archive-date{color:var(--text-muted);font-size:.875rem;min-width:60px}.archive-link{color:var(--text-primary);text-decoration:none;transition:color var(--transition-fast)}.archive-link:hover{color:var(--accent-primary)}.about-page{max-width:900px;margin:0 auto}.about-header{text-align:center;margin-bottom:var(--spacing-2xl);padding-bottom:var(--spacing-xl);border-bottom:1px solid var(--border-color)}.about-page-avatar{width:120px;height:120px;border-radius:50%;object-fit:cover;border:3px solid var(--accent-primary);margin-bottom:var(--spacing-lg)}.about-page-name{display:flex;flex-direction:column;align-items:center;gap:0;margin:0 0 var(--spacing-md);line-height:1.1;text-align:center}.about-page-description{font-size:1.25rem;color:var(--text-secondary);margin:0 0 var(--spacing-lg);font-style:italic}.about-page-social{margin-top:var(--spacing-md)}.about-content-full{font-size:1.125rem;line-height:1.8;color:var(--text-primary)}.about-content-full h2{font-size:1.75rem;margin-top:var(--spacing-xl);margin-bottom:var(--spacing-md);color:var(--text-primary)}.about-content-full p{margin-bottom:var(--spacing-md)}.about-content-full a{color:var(--accent-primary);text-decoration:underline}.about-content-full a:hover{color:var(--accent-hover)}.site-footer{margin-top:var(--spacing-2xl);padding:var(--spacing-xl)var(--spacing-lg);border-top:1px solid var(--border-color);background-color:var(--bg-secondary)}.footer-content{max-width:1200px;margin:0 auto;text-align:center}.footer-text{color:var(--text-muted);font-size:.875rem;margin-bottom:var(--spacing-md)}.footer-social{display:flex;justify-content:center;gap:var(--spacing-md)}.social-links{display:flex;gap:var(--spacing-md);justify-content:center}.social-link{color:var(--text-secondary);transition:color var(--transition-fast);display:flex;align-items:center}.social-link:hover{color:var(--accent-primary)}.social-link svg{width:var(--icon-md);height:var(--icon-md)}.scroll-to-top{position:fixed;bottom:calc(var(--spacing-lg) + 60px);right:var(--spacing-lg);width:50px;height:50px;border-radius:50%;background-color:transparent;color:var(--accent-primary);border:none;cursor:pointer;display:none;align-items:center;justify-content:center;box-shadow:none;transition:all var(--transition-base);z-index:999}.scroll-to-top.visible{display:flex}.scroll-to-top:hover{background-color:transparent;transform:translateY(-2px);box-shadow:none;color:var(--accent-hover)}.scroll-to-top svg{width:1.5rem;height:1.5rem}.buy-me-coffee{position:fixed;bottom:calc(var(--spacing-lg) + 60px);right:var(--spacing-lg);width:50px;height:50px;border-radius:50%;background-color:var(--yellow-accent);color:#000;display:flex;align-items:center;justify-content:center;box-shadow:var(--shadow-md);transition:all var(--transition-base);z-index:999;text-decoration:none}.buy-me-coffee:hover{background-color:var(--yellow-hover);transform:translateY(-2px);box-shadow:var(--shadow-lg)}.buy-me-coffee svg{width:1.5rem;height:1.5rem}.code-block-wrapper{position:relative;margin:var(--spacing-md)0}.code-block-copy{position:absolute;top:.5rem;right:calc(.5rem + 95px + .5rem);z-index:10;background:rgba(30,30,45,.9);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.1);border-radius:.375rem;padding:.375rem .75rem;color:var(--text-secondary);font-size:.75rem;font-weight:500;cursor:pointer;display:flex;align-items:center;gap:.375rem;transition:all var(--transition-fast);font-family:var(--font-body);white-space:nowrap}.code-block-copy:hover{background:var(--bg-tertiary);border-color:var(--accent-primary);color:var(--accent-primary)}.code-block-copy.copied{background:rgba(34,197,94,.2);border-color:rgba(34,197,94,.5);color:#22c55e}.code-block-copy svg{width:.875rem;height:.875rem;flex-shrink:0}[data-theme=light] .code-block-copy{background:rgba(255,255,255,.95);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid rgba(107,33,168,.2);color:var(--text-secondary);top:8px}[data-theme=light] .code-block-copy:hover{background:#fff;border-color:var(--accent-primary);color:var(--accent-primary)}[data-theme=light] .code-block-copy.copied{background:rgba(34,197,94,.15);border-color:rgba(34,197,94,.4);color:#16a34a}.code-block-toggle{position:absolute;top:.5rem;right:.5rem;z-index:10;background:rgba(30,30,45,.9);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.1);border-radius:.375rem;padding:.375rem .75rem;color:var(--text-secondary);font-size:.75rem;font-weight:500;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:.375rem;transition:all var(--transition-fast);font-family:var(--font-body);width:95px;white-space:nowrap;box-sizing:border-box}.code-block-toggle:hover{background:var(--bg-tertiary);border-color:var(--accent-primary);color:var(--accent-primary)}[data-theme=light] .code-block-toggle{background:rgba(255,255,255,.95);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid rgba(107,33,168,.2);color:var(--text-secondary);top:8px}[data-theme=light] .code-block-toggle:hover{background:#fff;border-color:var(--accent-primary);color:var(--accent-primary)}.code-block-toggle svg{width:.875rem;height:.875rem;transition:transform var(--transition-fast)}.code-block-wrapper.collapsed .code-block-toggle svg{transform:rotate(180deg)}.code-block-content{transition:opacity var(--transition-base);overflow:visible}.code-block-wrapper.collapsed .code-block-content{max-height:150px;overflow:hidden;opacity:.9;position:relative}.code-block-wrapper.collapsed .code-block-content::after{content:"";position:absolute;bottom:0;left:0;right:0;height:50px;background:linear-gradient(to bottom,transparent,var(--bg-primary));pointer-events:none}.post-content-main blockquote.blockquote-regular{margin:var(--spacing-md)0;padding:var(--spacing-md)var(--spacing-lg);border-left:4px solid var(--accent-primary);background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;color:var(--text-primary);position:relative}.post-content-main blockquote.blockquote-regular p{margin-bottom:0}.post-content-main blockquote.blockquote-regular p:last-child{margin-bottom:0}.post-content-main .alert{margin:var(--spacing-md)0;border-radius:.5rem;border:1px solid var(--border-color);background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);overflow:hidden;transition:all var(--transition-base)}.post-content-main .alert:hover{border-color:var(--accent-primary);box-shadow:var(--shadow-md)}.post-content-main .alert-header{display:flex;align-items:center;gap:var(--spacing-sm);padding:var(--spacing-md)var(--spacing-lg);font-weight:600;font-size:1rem;border-bottom:1px solid var(--border-color);transition:background-color var(--transition-fast)}.post-content-main .alert-collapsible .alert-header:hover{background-color:var(--bg-tertiary)}.post-content-main .alert-icon{font-size:1.25rem;flex-shrink:0;line-height:1}.post-content-main .alert-title{flex:1;color:var(--text-primary)}.post-content-main .alert-toggle{font-size:1.5rem;font-weight:300;color:var(--text-secondary);line-height:1;user-select:none;transition:transform var(--transition-fast)}.post-content-main .alert-collapsible[data-collapsed=true] .alert-toggle{transform:rotate(0)}.post-content-main .alert-collapsible[data-collapsed=false] .alert-toggle{transform:rotate(0)}.post-content-main .alert-content{padding:var(--spacing-md)var(--spacing-lg);color:var(--text-primary);line-height:1.8}.post-content-main .alert-content p{margin-bottom:var(--spacing-sm)}.post-content-main .alert-content p:last-child{margin-bottom:0}.post-content-main .alert-content ul,.post-content-main .alert-content ol{margin:var(--spacing-sm)0;padding-left:1.5em}.post-content-main .alert-content li{margin-bottom:.25em}.post-content-main .alert-content code{background-color:var(--bg-tertiary);padding:.125rem .375rem;border-radius:.25rem;font-size:.9em}.post-content-main .alert-content a{color:var(--accent-primary);text-decoration:underline}.post-content-main .alert-content a:hover{color:var(--accent-hover)}.post-content-main .alert-note{border-left:4px solid #3b82f6}.post-content-main .alert-note .alert-header{background-color:rgba(59,130,246,.1)}[data-theme=light] .post-content-main .alert-note .alert-header{background-color:rgba(59,130,246,.15)}.post-content-main .alert-tip{border-left:4px solid #10b981}.post-content-main .alert-tip .alert-header{background-color:rgba(16,185,129,.1)}[data-theme=light] .post-content-main .alert-tip .alert-header{background-color:rgba(16,185,129,.15)}.post-content-main .alert-important{border-left:4px solid #f59e0b}.post-content-main .alert-important .alert-header{background-color:rgba(245,158,11,.1)}[data-theme=light] .post-content-main .alert-important .alert-header{background-color:rgba(245,158,11,.15)}.post-content-main .alert-warning{border-left:4px solid #f59e0b}.post-content-main .alert-warning .alert-header{background-color:rgba(245,158,11,.1)}[data-theme=light] .post-content-main .alert-warning .alert-header{background-color:rgba(245,158,11,.15)}.post-content-main .alert-caution{border-left:4px solid #ef4444}.post-content-main .alert-caution .alert-header{background-color:rgba(239,68,68,.1)}[data-theme=light] .post-content-main .alert-caution .alert-header{background-color:rgba(239,68,68,.15)}.post-content-main .alert-info{border-left:4px solid #3b82f6}.post-content-main .alert-info .alert-header{background-color:rgba(59,130,246,.1)}[data-theme=light] .post-content-main .alert-info .alert-header{background-color:rgba(59,130,246,.15)}.post-content-main .alert-success{border-left:4px solid #10b981}.post-content-main .alert-success .alert-header{background-color:rgba(16,185,129,.1)}[data-theme=light] .post-content-main .alert-success .alert-header{background-color:rgba(16,185,129,.15)}.post-content-main .alert-question{border-left:4px solid #8b5cf6}.post-content-main .alert-question .alert-header{background-color:rgba(139,92,246,.1)}[data-theme=light] .post-content-main .alert-question .alert-header{background-color:rgba(139,92,246,.15)}.post-content-main .alert-danger{border-left:4px solid #ef4444}.post-content-main .alert-danger .alert-header{background-color:rgba(239,68,68,.1)}[data-theme=light] .post-content-main .alert-danger .alert-header{background-color:rgba(239,68,68,.15)}.post-content-main .alert-bug{border-left:4px solid #f97316}.post-content-main .alert-bug .alert-header{background-color:rgba(249,115,22,.1)}[data-theme=light] .post-content-main .alert-bug .alert-header{background-color:rgba(249,115,22,.15)}.post-content-main .alert-example{border-left:4px solid #6366f1}.post-content-main .alert-example .alert-header{background-color:rgba(99,102,241,.1)}[data-theme=light] .post-content-main .alert-example .alert-header{background-color:rgba(99,102,241,.15)}[data-theme=light] .post-content-main blockquote.blockquote-regular{background-color:var(--glass-block);border-left-color:var(--accent-primary)}[data-theme=light] .post-content-main .alert{background-color:var(--glass-block)}[data-theme=light] .post-content-main .alert-content code{background-color:rgba(107,33,168,.1)}@media(max-width:768px){.post-content-main blockquote.blockquote-regular{margin:var(--spacing-sm)0;padding:var(--spacing-sm)var(--spacing-md)}.post-content-main .alert{margin:var(--spacing-sm)0}.post-content-main .alert-header{padding:var(--spacing-sm)var(--spacing-md);font-size:.9rem}.post-content-main .alert-content{padding:var(--spacing-sm)var(--spacing-md)}.post-content-main .alert-icon{font-size:1.1rem}}.pagination{margin:var(--spacing-2xl)0;padding:var(--spacing-lg);display:flex;flex-direction:column;gap:var(--spacing-md);align-items:center;background-color:var(--bg-secondary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:.5rem;border:1px solid var(--border-color)}.pagination-info{display:flex;flex-direction:column;align-items:center;gap:var(--spacing-xs);text-align:center}.pagination-text{font-size:.875rem;color:var(--text-secondary);font-weight:500}.pagination-current{font-weight:700;color:var(--accent-primary)}.pagination-total{font-weight:600;color:var(--text-primary)}.pagination-count{font-size:.75rem;color:var(--text-muted)}.pagination-controls{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--spacing-sm)}.pagination-link{display:flex;align-items:center;gap:var(--spacing-xs);padding:var(--spacing-sm)var(--spacing-md);background-color:var(--bg-tertiary);color:var(--text-primary);text-decoration:none;border-radius:.375rem;border:1px solid var(--border-color);font-size:.875rem;font-weight:500;transition:all var(--transition-fast);cursor:pointer;min-height:2.5rem;min-width:2.5rem;justify-content:center}.pagination-link:hover:not(.pagination-link-disabled){background-color:var(--accent-primary);color:var(--bg-primary);border-color:var(--accent-primary);box-shadow:var(--shadow-sm);transform:translateY(-1px)}.pagination-link:focus-visible{outline:2px solid var(--accent-primary);outline-offset:2px}.pagination-link-disabled{opacity:.4;cursor:not-allowed;background-color:var(--bg-secondary);color:var(--text-muted)}.pagination-icon{width:1rem;height:1rem;flex-shrink:0}.pagination-link-text{white-space:nowrap}@media(max-width:640px){.pagination-link-first .pagination-link-text,.pagination-link-last .pagination-link-text{display:none}.pagination-link-prev .pagination-link-text,.pagination-link-next .pagination-link-text{display:none}}.pagination-numbers{display:flex;align-items:center;gap:var(--spacing-xs);margin:0 var(--spacing-sm)}.pagination-number{display:flex;align-items:center;justify-content:center;min-width:2.5rem;height:2.5rem;padding:0 var(--spacing-sm);background-color:var(--bg-tertiary);color:var(--text-primary);text-decoration:none;border-radius:.375rem;border:1px solid var(--border-color);font-size:.875rem;font-weight:500;transition:all var(--transition-fast);cursor:pointer}.pagination-number:hover{background-color:var(--accent-primary);color:var(--bg-primary);border-color:var(--accent-primary);box-shadow:var(--shadow-sm);transform:translateY(-1px)}.pagination-number:focus-visible{outline:2px solid var(--accent-primary);outline-offset:2px}.pagination-number-current{background-color:var(--accent-primary);color:var(--bg-primary);border-color:var(--accent-primary);font-weight:700;cursor:default;box-shadow:var(--shadow-sm)}.pagination-number-current:hover{transform:none;background-color:var(--accent-primary);color:var(--bg-primary)}.pagination-ellipsis{display:flex;align-items:center;justify-content:center;min-width:2.5rem;height:2.5rem;color:var(--text-muted);font-size:.875rem;user-select:none}[data-theme=light] .pagination{background-color:var(--glass-block)}[data-theme=light] .pagination-link{background-color:rgba(107,33,168,.1)}[data-theme=light] .pagination-link:hover:not(.pagination-link-disabled){background-color:var(--accent-primary);color:#fff}[data-theme=light] .pagination-number{background-color:rgba(107,33,168,.1)}[data-theme=light] .pagination-number:hover{background-color:var(--accent-primary);color:#fff}[data-theme=light] .pagination-number-current{background-color:var(--accent-primary);color:#fff}@media(max-width:768px){.pagination{padding:var(--spacing-md);gap:var(--spacing-sm)}.pagination-controls{gap:var(--spacing-xs)}.pagination-numbers{margin:0 var(--spacing-xs);gap:.25rem}.pagination-number{min-width:2.25rem;height:2.25rem;font-size:.8125rem}.pagination-link{padding:var(--spacing-xs)var(--spacing-sm);min-height:2.25rem;min-width:2.25rem;font-size:.8125rem}.pagination-icon{width:.875rem;height:.875rem}}@media(max-width:480px){.pagination-info{font-size:.8125rem}.pagination-count{font-size:.6875rem}.pagination-numbers{max-width:100%;overflow-x:auto;scrollbar-width:thin;-webkit-overflow-scrolling:touch}.pagination-numbers::-webkit-scrollbar{height:4px}.pagination-numbers::-webkit-scrollbar-track{background:var(--bg-secondary);border-radius:2px}.pagination-numbers::-webkit-scrollbar-thumb{background:var(--accent-primary);border-radius:2px}}.search-modal{position:fixed;inset:0;z-index:1000;display:none;align-items:flex-start;justify-content:center;padding-top:20vh;overflow-y:auto}.search-modal[aria-hidden=false]{display:flex}.search-modal-backdrop{position:absolute;inset:0;background-color:rgba(0,0,0,.6);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);animation:fadeIn var(--transition-base)}.search-modal-container{position:relative;width:90%;max-width:600px;z-index:1001;flex-shrink:0;animation:slideDown var(--transition-base)}.search-input-wrapper{position:relative;display:flex;align-items:center;background:rgba(30,30,45,.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:12px;border:1px solid transparent;box-shadow:0 10px 40px rgba(0,0,0,.4),0 0 10px rgba(160,32,240,.1);overflow:hidden}.search-input-icon{position:absolute;left:var(--spacing-lg);width:1.5rem;height:1.5rem;color:rgba(255,255,255,.5);pointer-events:none;flex-shrink:0;z-index:1}.search-input{flex:1;height:60px;padding:0 calc( var(--spacing-lg) + 2rem + var(--spacing-sm) + 2rem + var(--spacing-lg) )0 calc(var(--spacing-lg) + 1.5rem + var(--spacing-md));background:0 0;border:none;color:rgba(255,255,255,.95);font-family:var(--font-body);font-size:1.2rem;transition:all var(--transition-fast)}.search-input::-webkit-search-cancel-button{display:none;-webkit-appearance:none}.search-input::-ms-clear{display:none}.search-input::placeholder{color:rgba(255,255,255,.4)}.search-input:focus{outline:none}.search-input-clear{position:absolute;right:calc(var(--spacing-lg) + 2rem + var(--spacing-sm));background:0 0;border:none;color:rgba(255,255,255,.5);cursor:pointer;padding:var(--spacing-sm);display:none;align-items:center;justify-content:center;transition:color var(--transition-fast);z-index:2;width:2rem;height:2rem;border-radius:.25rem}.search-input-clear.visible{display:flex}.search-input-clear:hover{color:var(--accent-primary)}.search-input-clear svg{width:1.25rem;height:1.25rem}.search-modal-close{position:absolute;right:var(--spacing-lg);background:0 0;border:none;color:rgba(255,255,255,.5);cursor:pointer;padding:var(--spacing-sm);display:flex;align-items:center;justify-content:center;transition:color var(--transition-fast);z-index:2;width:2rem;height:2rem;border-radius:.25rem}.search-modal-close:hover{color:var(--accent-primary)}.search-modal-close svg{width:1.25rem;height:1.25rem}.search-results{margin-top:.5rem;max-height:65vh;overflow-y:auto;background:rgba(30,30,45,.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-radius:12px;border:1px solid transparent;box-shadow:0 10px 40px rgba(0,0,0,.4),0 0 10px rgba(160,32,240,.1);display:none}.search-results:not(:empty){display:block}.search-result-item{display:block;padding:var(--spacing-md)var(--spacing-lg);border-bottom:1px solid rgba(255,255,255,.15);text-decoration:none;transition:background-color var(--transition-fast)}.search-result-item:last-child{border-bottom:none}.search-result-item:hover{background-color:rgba(255,255,255,5%)}.search-result-title{font-weight:700;color:#fff;margin-bottom:.25rem;font-size:1rem;line-height:1.4}.search-result-title mark{background-color:rgba(160,32,240,.3);color:#fff;padding:.125rem 0;border-radius:.125rem;font-weight:700;display:inline-block;vertical-align:baseline}.search-result-summary{color:rgba(255,255,255,.6);font-size:.875rem;margin-bottom:.25rem;line-height:1.5}.search-result-summary mark{background-color:rgba(160,32,240,.2);color:rgba(255,255,255,.9);padding:.125rem 0;border-radius:.125rem;display:inline-block}.search-result-date{display:flex;align-items:center;gap:.375rem;color:rgba(255,255,255,.4);font-size:.75rem;margin-top:.25rem}.search-result-date-icon{width:.875rem;height:.875rem}.search-result-empty{padding:var(--spacing-xl);text-align:center;color:rgba(255,255,255,.5)}@media(max-width:768px){.search-modal{padding-top:8vh}.search-results{max-height:60vh}}@keyframes fadeIn{from{opacity:0}to{opacity:1}}@keyframes slideDown{from{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translateY(0)}}@media(max-width:768px){.hero-content{grid-template-columns:1fr;gap:var(--spacing-xl);text-align:center}.hero-image{order:-1}.hero-logo,.hero-logo-placeholder{max-width:150px}.hero-title-line1,.hero-title-line2{font-size:clamp(2rem,8vw,3rem)}}@media(max-width:1024px){.single-post-wrapper{flex-direction:column}.post-toc{position:sticky;top:70px;width:100%;margin-bottom:var(--spacing-lg);order:-1}.toc-content.expanded{max-height:300px}}@media(max-width:1024px){.header-logo .logo-link{font-size:1.125rem}}@media(max-width:768px){.search-modal-container{width:90%}.header-container{padding:0 var(--spacing-sm)}.header-content{gap:var(--spacing-xs)}.header-logo{flex:auto;min-width:0}.header-logo .logo-link{font-size:.875rem;max-width:100%}.header-menu{gap:var(--spacing-xs);flex-shrink:0}.menu-list{gap:var(--spacing-xs)}.menu-link{padding:var(--spacing-xs)var(--spacing-sm);font-size:.875rem;white-space:nowrap}.search-toggle,.theme-toggle{padding:var(--spacing-xs);width:1.5rem;height:1.5rem;flex-shrink:0}.search-icon{width:var(--icon-sm);height:var(--icon-sm)}.theme-toggle .icon-sun,.theme-toggle .icon-moon{width:1rem;height:1rem}main{padding:var(--spacing-lg)var(--spacing-md)}.post-card{flex-direction:column}.post-image{width:100%;height:200px}.post-title-main{font-size:2rem}.year-posts{margin-left:0}.post-content-main pre,.post-content-main .highlight,.post-content-main pre.highlight{max-width:100%;width:100%;box-sizing:border-box;overflow-x:auto;-webkit-overflow-scrolling:touch}.post-content-main pre code{min-width:0}.single-post{width:100%;max-width:100%;box-sizing:border-box}.post-content-main{width:100%;max-width:100%;box-sizing:border-box;overflow-x:hidden}.code-block-copy{right:calc(.5rem + 85px + .5rem);padding:.375rem .5rem;font-size:.7rem;width:auto;min-width:32px;box-sizing:border-box;margin-right:0}.code-block-copy .copy-text,.code-block-copy .copied-text{display:none}.code-block-toggle{padding:.375rem .5rem;font-size:.7rem;width:85px;box-sizing:border-box}}@media(max-width:480px){.header-container{padding:0 var(--spacing-xs)}.header-content{gap:.25rem}.header-logo{flex:auto;min-width:0}.header-logo .logo-link{font-size:.75rem;max-width:100%}.header-menu{gap:.25rem}.menu-list{gap:.25rem}.menu-link{padding:var(--spacing-xs);font-size:.75rem}.about-content{gap:var(--spacing-sm)}.about-avatar{width:60px;height:60px}.about-name{font-size:1.25rem}.post-title-main{font-size:1.75rem}.tags-list{gap:var(--spacing-sm)}.tag-chip-large{padding:var(--spacing-xs)var(--spacing-sm);font-size:.875rem}.code-block-copy{right:calc(1rem + 85px)}} \ No newline at end of file diff --git a/public/css/syntax-dark.min.3e403a03e3af837b3829e9b6f01fc7792bda7cb7f5056f5e9786109545c6b2e1.css b/public/css/syntax-dark.min.3e403a03e3af837b3829e9b6f01fc7792bda7cb7f5056f5e9786109545c6b2e1.css new file mode 100755 index 0000000..3af6a82 --- /dev/null +++ b/public/css/syntax-dark.min.3e403a03e3af837b3829e9b6f01fc7792bda7cb7f5056f5e9786109545c6b2e1.css @@ -0,0 +1 @@ +.bg{color:#cad3f5;background-color:#24273a}.chroma{color:#cad3f5;background-color:#24273a}.chroma .err{color:#ed8796}.chroma .lnlinks{outline:none;text-decoration:none;color:inherit}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{background-color:#494d64}.chroma .lnt{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#8087a2}.chroma .ln{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#8087a2}.chroma .line{display:flex}.chroma .k{color:#c6a0f6}.chroma .kc{color:#f5a97f}.chroma .kd{color:#ed8796}.chroma .kn{color:#8bd5ca}.chroma .kp{color:#c6a0f6}.chroma .kr{color:#c6a0f6}.chroma .kt{color:#ed8796}.chroma .na{color:#8aadf4}.chroma .nc{color:#eed49f}.chroma .no{color:#eed49f}.chroma .nd{color:#8aadf4;font-weight:700}.chroma .ni{color:#8bd5ca}.chroma .ne{color:#f5a97f}.chroma .nl{color:#91d7e3}.chroma .nn{color:#f5a97f}.chroma .py{color:#f5a97f}.chroma .nt{color:#c6a0f6}.chroma .nb{color:#91d7e3}.chroma .bp{color:#91d7e3}.chroma .nv{color:#f4dbd6}.chroma .vc{color:#f4dbd6}.chroma .vg{color:#f4dbd6}.chroma .vi{color:#f4dbd6}.chroma .vm{color:#f4dbd6}.chroma .nf{color:#8aadf4}.chroma .fm{color:#8aadf4}.chroma .s{color:#a6da95}.chroma .sa{color:#ed8796}.chroma .sb{color:#a6da95}.chroma .sc{color:#a6da95}.chroma .dl{color:#8aadf4}.chroma .sd{color:#6e738d}.chroma .s2{color:#a6da95}.chroma .se{color:#8aadf4}.chroma .sh{color:#6e738d}.chroma .si{color:#a6da95}.chroma .sx{color:#a6da95}.chroma .sr{color:#8bd5ca}.chroma .s1{color:#a6da95}.chroma .ss{color:#a6da95}.chroma .m{color:#f5a97f}.chroma .mb{color:#f5a97f}.chroma .mf{color:#f5a97f}.chroma .mh{color:#f5a97f}.chroma .mi{color:#f5a97f}.chroma .il{color:#f5a97f}.chroma .mo{color:#f5a97f}.chroma .o{color:#91d7e3;font-weight:700}.chroma .ow{color:#91d7e3;font-weight:700}.chroma .c{color:#6e738d;font-style:italic}.chroma .ch{color:#6e738d;font-style:italic}.chroma .cm{color:#6e738d;font-style:italic}.chroma .c1{color:#6e738d;font-style:italic}.chroma .cs{color:#6e738d;font-style:italic}.chroma .cp{color:#6e738d;font-style:italic}.chroma .cpf{color:#6e738d;font-weight:700;font-style:italic}.chroma .gd{color:#ed8796;background-color:#363a4f}.chroma .ge{font-style:italic}.chroma .gr{color:#ed8796}.chroma .gh{color:#f5a97f;font-weight:700}.chroma .gi{color:#a6da95;background-color:#363a4f}.chroma .gs{font-weight:700}.chroma .gu{color:#f5a97f;font-weight:700}.chroma .gt{color:#ed8796}.chroma .gl{text-decoration:underline} \ No newline at end of file diff --git a/public/css/syntax-dark.min.420301e411f77948a12733bf5addb3bd50f2e07c2a1c9809f307d07f72b8424a.css b/public/css/syntax-dark.min.420301e411f77948a12733bf5addb3bd50f2e07c2a1c9809f307d07f72b8424a.css new file mode 100755 index 0000000..e4c1ad6 --- /dev/null +++ b/public/css/syntax-dark.min.420301e411f77948a12733bf5addb3bd50f2e07c2a1c9809f307d07f72b8424a.css @@ -0,0 +1 @@ +.bg{color:#cad3f5;background-color:#24273a}.chroma{color:#cad3f5;background-color:#24273a}.chroma .x{}.chroma .err{color:#ed8796}.chroma .cl{}.chroma .lnlinks{outline:none;text-decoration:none;color:inherit}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{background-color:#494d64}.chroma .lnt{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#8087a2}.chroma .ln{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#8087a2}.chroma .line{display:flex}.chroma .k{color:#c6a0f6}.chroma .kc{color:#f5a97f}.chroma .kd{color:#ed8796}.chroma .kn{color:#8bd5ca}.chroma .kp{color:#c6a0f6}.chroma .kr{color:#c6a0f6}.chroma .kt{color:#ed8796}.chroma .n{}.chroma .na{color:#8aadf4}.chroma .nc{color:#eed49f}.chroma .no{color:#eed49f}.chroma .nd{color:#8aadf4;font-weight:700}.chroma .ni{color:#8bd5ca}.chroma .ne{color:#f5a97f}.chroma .nl{color:#91d7e3}.chroma .nn{color:#f5a97f}.chroma .nx{}.chroma .py{color:#f5a97f}.chroma .nt{color:#c6a0f6}.chroma .nb{color:#91d7e3}.chroma .bp{color:#91d7e3}.chroma .nv{color:#f4dbd6}.chroma .vc{color:#f4dbd6}.chroma .vg{color:#f4dbd6}.chroma .vi{color:#f4dbd6}.chroma .vm{color:#f4dbd6}.chroma .nf{color:#8aadf4}.chroma .fm{color:#8aadf4}.chroma .l{}.chroma .ld{}.chroma .s{color:#a6da95}.chroma .sa{color:#ed8796}.chroma .sb{color:#a6da95}.chroma .sc{color:#a6da95}.chroma .dl{color:#8aadf4}.chroma .sd{color:#6e738d}.chroma .s2{color:#a6da95}.chroma .se{color:#8aadf4}.chroma .sh{color:#6e738d}.chroma .si{color:#a6da95}.chroma .sx{color:#a6da95}.chroma .sr{color:#8bd5ca}.chroma .s1{color:#a6da95}.chroma .ss{color:#a6da95}.chroma .m{color:#f5a97f}.chroma .mb{color:#f5a97f}.chroma .mf{color:#f5a97f}.chroma .mh{color:#f5a97f}.chroma .mi{color:#f5a97f}.chroma .il{color:#f5a97f}.chroma .mo{color:#f5a97f}.chroma .o{color:#91d7e3;font-weight:700}.chroma .ow{color:#91d7e3;font-weight:700}.chroma .p{}.chroma .c{color:#6e738d;font-style:italic}.chroma .ch{color:#6e738d;font-style:italic}.chroma .cm{color:#6e738d;font-style:italic}.chroma .c1{color:#6e738d;font-style:italic}.chroma .cs{color:#6e738d;font-style:italic}.chroma .cp{color:#6e738d;font-style:italic}.chroma .cpf{color:#6e738d;font-weight:700;font-style:italic}.chroma .g{}.chroma .gd{color:#ed8796;background-color:#363a4f}.chroma .ge{font-style:italic}.chroma .gr{color:#ed8796}.chroma .gh{color:#f5a97f;font-weight:700}.chroma .gi{color:#a6da95;background-color:#363a4f}.chroma .go{}.chroma .gp{}.chroma .gs{font-weight:700}.chroma .gu{color:#f5a97f;font-weight:700}.chroma .gt{color:#ed8796}.chroma .gl{text-decoration:underline}.chroma .w{} \ No newline at end of file diff --git a/public/css/syntax-dark.min.b01037e5609e6260b74d6fcdd6d5131e8dcbc4d46d6ef4bfbe5a60bdfc1a9f30.css b/public/css/syntax-dark.min.b01037e5609e6260b74d6fcdd6d5131e8dcbc4d46d6ef4bfbe5a60bdfc1a9f30.css new file mode 100755 index 0000000..95f3a2a --- /dev/null +++ b/public/css/syntax-dark.min.b01037e5609e6260b74d6fcdd6d5131e8dcbc4d46d6ef4bfbe5a60bdfc1a9f30.css @@ -0,0 +1 @@ + .bg{color:#cad3f5;background-color:#24273a}.chroma{color:#cad3f5;background-color:#24273a}.chroma .err{color:#ed8796}.chroma .lnlinks{outline:none;text-decoration:none;color:inherit}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{background-color:#494d64}.chroma .lnt{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#8087a2}.chroma .ln{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#8087a2}.chroma .line{display:flex}.chroma .k{color:#c6a0f6}.chroma .kc{color:#f5a97f}.chroma .kd{color:#ed8796}.chroma .kn{color:#8bd5ca}.chroma .kp{color:#c6a0f6}.chroma .kr{color:#c6a0f6}.chroma .kt{color:#ed8796}.chroma .na{color:#8aadf4}.chroma .nc{color:#eed49f}.chroma .no{color:#eed49f}.chroma .nd{color:#8aadf4;font-weight:700}.chroma .ni{color:#8bd5ca}.chroma .ne{color:#f5a97f}.chroma .nl{color:#91d7e3}.chroma .nn{color:#f5a97f}.chroma .py{color:#f5a97f}.chroma .nt{color:#c6a0f6}.chroma .nb{color:#91d7e3}.chroma .bp{color:#91d7e3}.chroma .nv{color:#f4dbd6}.chroma .vc{color:#f4dbd6}.chroma .vg{color:#f4dbd6}.chroma .vi{color:#f4dbd6}.chroma .vm{color:#f4dbd6}.chroma .nf{color:#8aadf4}.chroma .fm{color:#8aadf4}.chroma .s{color:#a6da95}.chroma .sa{color:#ed8796}.chroma .sb{color:#a6da95}.chroma .sc{color:#a6da95}.chroma .dl{color:#8aadf4}.chroma .sd{color:#6e738d}.chroma .s2{color:#a6da95}.chroma .se{color:#8aadf4}.chroma .sh{color:#6e738d}.chroma .si{color:#a6da95}.chroma .sx{color:#a6da95}.chroma .sr{color:#8bd5ca}.chroma .s1{color:#a6da95}.chroma .ss{color:#a6da95}.chroma .m{color:#f5a97f}.chroma .mb{color:#f5a97f}.chroma .mf{color:#f5a97f}.chroma .mh{color:#f5a97f}.chroma .mi{color:#f5a97f}.chroma .il{color:#f5a97f}.chroma .mo{color:#f5a97f}.chroma .o{color:#91d7e3;font-weight:700}.chroma .ow{color:#91d7e3;font-weight:700}.chroma .c{color:#6e738d;font-style:italic}.chroma .ch{color:#5b6078;font-style:italic}.chroma .cm{color:#6e738d;font-style:italic}.chroma .c1{color:#6e738d;font-style:italic}.chroma .cs{color:#6e738d;font-style:italic}.chroma .cp{color:#6e738d;font-style:italic}.chroma .cpf{color:#6e738d;font-weight:700;font-style:italic}.chroma .gd{color:#ed8796;background-color:#363a4f}.chroma .ge{font-style:italic}.chroma .gr{color:#ed8796}.chroma .gh{color:#f5a97f;font-weight:700}.chroma .gi{color:#a6da95;background-color:#363a4f}.chroma .gs{font-weight:700}.chroma .gu{color:#f5a97f;font-weight:700}.chroma .gt{color:#ed8796}.chroma .gl{text-decoration:underline} \ No newline at end of file diff --git a/public/css/syntax-light.min.6d7aa9da4e25e95c94a2d28f13e50fc17538b6de076d0afe7999d86f8ccdcc79.css b/public/css/syntax-light.min.6d7aa9da4e25e95c94a2d28f13e50fc17538b6de076d0afe7999d86f8ccdcc79.css new file mode 100755 index 0000000..89c6ef8 --- /dev/null +++ b/public/css/syntax-light.min.6d7aa9da4e25e95c94a2d28f13e50fc17538b6de076d0afe7999d86f8ccdcc79.css @@ -0,0 +1 @@ +.bg{color:#c6d0f5;background-color:#303446}.chroma{color:#c6d0f5;background-color:#303446}.chroma .x{}.chroma .err{color:#e78284}.chroma .cl{}.chroma .lnlinks{outline:none;text-decoration:none;color:inherit}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{background-color:#51576d}.chroma .lnt{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#838ba7}.chroma .ln{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#838ba7}.chroma .line{display:flex}.chroma .k{color:#ca9ee6}.chroma .kc{color:#ef9f76}.chroma .kd{color:#e78284}.chroma .kn{color:#81c8be}.chroma .kp{color:#ca9ee6}.chroma .kr{color:#ca9ee6}.chroma .kt{color:#e78284}.chroma .n{}.chroma .na{color:#8caaee}.chroma .nc{color:#e5c890}.chroma .no{color:#e5c890}.chroma .nd{color:#8caaee;font-weight:700}.chroma .ni{color:#81c8be}.chroma .ne{color:#ef9f76}.chroma .nl{color:#99d1db}.chroma .nn{color:#ef9f76}.chroma .nx{}.chroma .py{color:#ef9f76}.chroma .nt{color:#ca9ee6}.chroma .nb{color:#99d1db}.chroma .bp{color:#99d1db}.chroma .nv{color:#f2d5cf}.chroma .vc{color:#f2d5cf}.chroma .vg{color:#f2d5cf}.chroma .vi{color:#f2d5cf}.chroma .vm{color:#f2d5cf}.chroma .nf{color:#8caaee}.chroma .fm{color:#8caaee}.chroma .l{}.chroma .ld{}.chroma .s{color:#a6d189}.chroma .sa{color:#e78284}.chroma .sb{color:#a6d189}.chroma .sc{color:#a6d189}.chroma .dl{color:#8caaee}.chroma .sd{color:#737994}.chroma .s2{color:#a6d189}.chroma .se{color:#8caaee}.chroma .sh{color:#737994}.chroma .si{color:#a6d189}.chroma .sx{color:#a6d189}.chroma .sr{color:#81c8be}.chroma .s1{color:#a6d189}.chroma .ss{color:#a6d189}.chroma .m{color:#ef9f76}.chroma .mb{color:#ef9f76}.chroma .mf{color:#ef9f76}.chroma .mh{color:#ef9f76}.chroma .mi{color:#ef9f76}.chroma .il{color:#ef9f76}.chroma .mo{color:#ef9f76}.chroma .o{color:#99d1db;font-weight:700}.chroma .ow{color:#99d1db;font-weight:700}.chroma .p{}.chroma .c{color:#737994;font-style:italic}.chroma .ch{color:#737994;font-style:italic}.chroma .cm{color:#737994;font-style:italic}.chroma .c1{color:#737994;font-style:italic}.chroma .cs{color:#737994;font-style:italic}.chroma .cp{color:#737994;font-style:italic}.chroma .cpf{color:#737994;font-weight:700;font-style:italic}.chroma .g{}.chroma .gd{color:#e78284;background-color:#414559}.chroma .ge{font-style:italic}.chroma .gr{color:#e78284}.chroma .gh{color:#ef9f76;font-weight:700}.chroma .gi{color:#a6d189;background-color:#414559}.chroma .go{}.chroma .gp{}.chroma .gs{font-weight:700}.chroma .gu{color:#ef9f76;font-weight:700}.chroma .gt{color:#e78284}.chroma .gl{text-decoration:underline}.chroma .w{} \ No newline at end of file diff --git a/public/css/syntax-light.min.9602bc2fcef52b4c62e05eae14b48ba428e114f116e993e504d83ed5a3aeda98.css b/public/css/syntax-light.min.9602bc2fcef52b4c62e05eae14b48ba428e114f116e993e504d83ed5a3aeda98.css new file mode 100755 index 0000000..85d29d3 --- /dev/null +++ b/public/css/syntax-light.min.9602bc2fcef52b4c62e05eae14b48ba428e114f116e993e504d83ed5a3aeda98.css @@ -0,0 +1 @@ + .bg{color:#c6d0f5;background-color:#303446}.chroma{color:#c6d0f5;background-color:#303446}.chroma .err{color:#e78284}.chroma .lnlinks{outline:none;text-decoration:none;color:inherit}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{background-color:#51576d}.chroma .lnt{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#838ba7}.chroma .ln{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#838ba7}.chroma .line{display:flex}.chroma .k{color:#ca9ee6}.chroma .kc{color:#ef9f76}.chroma .kd{color:#e78284}.chroma .kn{color:#81c8be}.chroma .kp{color:#ca9ee6}.chroma .kr{color:#ca9ee6}.chroma .kt{color:#e78284}.chroma .na{color:#8caaee}.chroma .nc{color:#e5c890}.chroma .no{color:#e5c890}.chroma .nd{color:#8caaee;font-weight:700}.chroma .ni{color:#81c8be}.chroma .ne{color:#ef9f76}.chroma .nl{color:#99d1db}.chroma .nn{color:#ef9f76}.chroma .py{color:#ef9f76}.chroma .nt{color:#ca9ee6}.chroma .nb{color:#99d1db}.chroma .bp{color:#99d1db}.chroma .nv{color:#f2d5cf}.chroma .vc{color:#f2d5cf}.chroma .vg{color:#f2d5cf}.chroma .vi{color:#f2d5cf}.chroma .vm{color:#f2d5cf}.chroma .nf{color:#8caaee}.chroma .fm{color:#8caaee}.chroma .s{color:#a6d189}.chroma .sa{color:#e78284}.chroma .sb{color:#a6d189}.chroma .sc{color:#a6d189}.chroma .dl{color:#8caaee}.chroma .sd{color:#737994}.chroma .s2{color:#a6d189}.chroma .se{color:#8caaee}.chroma .sh{color:#737994}.chroma .si{color:#a6d189}.chroma .sx{color:#a6d189}.chroma .sr{color:#81c8be}.chroma .s1{color:#a6d189}.chroma .ss{color:#a6d189}.chroma .m{color:#ef9f76}.chroma .mb{color:#ef9f76}.chroma .mf{color:#ef9f76}.chroma .mh{color:#ef9f76}.chroma .mi{color:#ef9f76}.chroma .il{color:#ef9f76}.chroma .mo{color:#ef9f76}.chroma .o{color:#99d1db;font-weight:700}.chroma .ow{color:#99d1db;font-weight:700}.chroma .c{color:#737994;font-style:italic}.chroma .ch{color:#626880;font-style:italic}.chroma .cm{color:#737994;font-style:italic}.chroma .c1{color:#737994;font-style:italic}.chroma .cs{color:#737994;font-style:italic}.chroma .cp{color:#737994;font-style:italic}.chroma .cpf{color:#737994;font-weight:700;font-style:italic}.chroma .gd{color:#e78284;background-color:#414559}.chroma .ge{font-style:italic}.chroma .gr{color:#e78284}.chroma .gh{color:#ef9f76;font-weight:700}.chroma .gi{color:#a6d189;background-color:#414559}.chroma .gs{font-weight:700}.chroma .gu{color:#ef9f76;font-weight:700}.chroma .gt{color:#e78284}.chroma .gl{text-decoration:underline} \ No newline at end of file diff --git a/public/css/syntax-light.min.d0d33b879698595e6b2c0f75f0cea95a8517fb0150570cd0ee4dc42e25c8d147.css b/public/css/syntax-light.min.d0d33b879698595e6b2c0f75f0cea95a8517fb0150570cd0ee4dc42e25c8d147.css new file mode 100755 index 0000000..3b1cd08 --- /dev/null +++ b/public/css/syntax-light.min.d0d33b879698595e6b2c0f75f0cea95a8517fb0150570cd0ee4dc42e25c8d147.css @@ -0,0 +1 @@ +.bg{color:#c6d0f5;background-color:#303446}.chroma{color:#c6d0f5;background-color:#303446}.chroma .err{color:#e78284}.chroma .lnlinks{outline:none;text-decoration:none;color:inherit}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{background-color:#51576d}.chroma .lnt{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#838ba7}.chroma .ln{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#838ba7}.chroma .line{display:flex}.chroma .k{color:#ca9ee6}.chroma .kc{color:#ef9f76}.chroma .kd{color:#e78284}.chroma .kn{color:#81c8be}.chroma .kp{color:#ca9ee6}.chroma .kr{color:#ca9ee6}.chroma .kt{color:#e78284}.chroma .na{color:#8caaee}.chroma .nc{color:#e5c890}.chroma .no{color:#e5c890}.chroma .nd{color:#8caaee;font-weight:700}.chroma .ni{color:#81c8be}.chroma .ne{color:#ef9f76}.chroma .nl{color:#99d1db}.chroma .nn{color:#ef9f76}.chroma .py{color:#ef9f76}.chroma .nt{color:#ca9ee6}.chroma .nb{color:#99d1db}.chroma .bp{color:#99d1db}.chroma .nv{color:#f2d5cf}.chroma .vc{color:#f2d5cf}.chroma .vg{color:#f2d5cf}.chroma .vi{color:#f2d5cf}.chroma .vm{color:#f2d5cf}.chroma .nf{color:#8caaee}.chroma .fm{color:#8caaee}.chroma .s{color:#a6d189}.chroma .sa{color:#e78284}.chroma .sb{color:#a6d189}.chroma .sc{color:#a6d189}.chroma .dl{color:#8caaee}.chroma .sd{color:#737994}.chroma .s2{color:#a6d189}.chroma .se{color:#8caaee}.chroma .sh{color:#737994}.chroma .si{color:#a6d189}.chroma .sx{color:#a6d189}.chroma .sr{color:#81c8be}.chroma .s1{color:#a6d189}.chroma .ss{color:#a6d189}.chroma .m{color:#ef9f76}.chroma .mb{color:#ef9f76}.chroma .mf{color:#ef9f76}.chroma .mh{color:#ef9f76}.chroma .mi{color:#ef9f76}.chroma .il{color:#ef9f76}.chroma .mo{color:#ef9f76}.chroma .o{color:#99d1db;font-weight:700}.chroma .ow{color:#99d1db;font-weight:700}.chroma .c{color:#737994;font-style:italic}.chroma .ch{color:#737994;font-style:italic}.chroma .cm{color:#737994;font-style:italic}.chroma .c1{color:#737994;font-style:italic}.chroma .cs{color:#737994;font-style:italic}.chroma .cp{color:#737994;font-style:italic}.chroma .cpf{color:#737994;font-weight:700;font-style:italic}.chroma .gd{color:#e78284;background-color:#414559}.chroma .ge{font-style:italic}.chroma .gr{color:#e78284}.chroma .gh{color:#ef9f76;font-weight:700}.chroma .gi{color:#a6d189;background-color:#414559}.chroma .gs{font-weight:700}.chroma .gu{color:#ef9f76;font-weight:700}.chroma .gt{color:#e78284}.chroma .gl{text-decoration:underline} \ No newline at end of file diff --git a/public/css/theme.min.86a32b729f656fc6c119ed69193950db815a3ad9fdc967b32c76d602f11449a4.css b/public/css/theme.min.86a32b729f656fc6c119ed69193950db815a3ad9fdc967b32c76d602f11449a4.css new file mode 100755 index 0000000..0fcd496 --- /dev/null +++ b/public/css/theme.min.86a32b729f656fc6c119ed69193950db815a3ad9fdc967b32c76d602f11449a4.css @@ -0,0 +1,18 @@ +*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}html{scroll-behavior:smooth;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}:root{--bg-deep:#020617;--accent-primary:#b063e0;--accent-mid-purple:#c485f6;--accent-light-purple:#dbabff;--accent-secondary:#00ffff;--glass-surface:rgba(255, 255, 255, 0.05);--glass-border:rgba(255, 255, 255, 0.1);--glass-block:rgb(255, 255, 255);--bg-primary:var(--bg-deep);--bg-secondary:var(--glass-surface);--bg-tertiary:rgba(255, 255, 255, 0.08);--text-primary:#f1f5f9;--text-secondary:#cbd5e1;--text-muted:#94a3b8;--accent-hover:#c485f6;--border-color:var(--glass-border);--accent-primary-rgba-03:rgba(176, 99, 224, 0.3);--accent-primary-rgba-05:rgba(176, 99, 224, 0.5);--accent-light-purple-rgba-01:rgba(219, 171, 255, 0.1);--accent-light-purple-rgba-03:rgba(219, 171, 255, 0.3);--accent-mid-purple-rgba-05:rgba(196, 133, 246, 0.05);--accent-mid-purple-rgba-01:rgba(196, 133, 246, 0.1);--accent-mid-purple-rgba-02:rgba(196, 133, 246, 0.2);--tag-bg:rgba(0, 0, 0, 0.4);--tag-color:var(--accent-light-purple);--shadow-sm:0 1px 2px 0 rgba(0, 0, 0, 0.3);--shadow-md:0 4px 6px -1px rgba(0, 0, 0, 0.4);--shadow-lg:0 10px 15px -3px rgba(0, 0, 0, 0.5);--yellow-accent:#ffdd00;--yellow-hover:#ffc700;--spacing-xs:0.25rem;--spacing-sm:0.5rem;--spacing-md:1rem;--spacing-lg:1.5rem;--spacing-xl:2rem;--spacing-2xl:3rem;--font-heading:"Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-body:"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, sans-serif;--font-logo:"Outfit", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-sans:var(--font-body);--font-mono:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace;--transition-fast:150ms ease-in-out;--transition-base:300ms ease-in-out;--transition-slow:500ms ease-in-out;--icon-sm:1rem;--icon-md:1.25rem;--icon-lg:1.5rem}[data-theme=light]{--bg-primary:#ffffff;--glass-surface:rgba( + 255, + 255, + 255, + 0.2 + );--glass-border:rgba( + 196, + 181, + 253, + 0.5 + );--glass-block:rgba(255, 255, 255, 0.8);--bg-secondary:var(--glass-surface);--bg-tertiary:rgba( + 255, + 255, + 255, + 0.25 + );--text-primary:#0f172a;--text-secondary:#475569;--text-muted:#64748b;--accent-primary:#6b21a8;--accent-mid-purple:#7c3aed;--accent-light-purple:#9333ea;--accent-secondary:#7e22ce;--accent-hover:#7c3aed;--border-color:var(--glass-border);--accent-primary-rgba-03:rgba(107, 33, 168, 0.3);--accent-primary-rgba-05:rgba(107, 33, 168, 0.5);--accent-light-purple-rgba-01:rgba(147, 51, 234, 0.1);--accent-light-purple-rgba-03:rgba(147, 51, 234, 0.3);--accent-mid-purple-rgba-05:rgba(124, 58, 237, 0.05);--accent-mid-purple-rgba-01:rgba(124, 58, 237, 0.1);--accent-mid-purple-rgba-02:rgba(124, 58, 237, 0.2);--tag-bg:rgba(124, 58, 237, 0.1);--tag-color:#6b21a8;--shadow-sm:0 1px 2px 0 rgba(0, 0, 0, 0.05);--shadow-md:0 4px 6px -1px rgba(0, 0, 0, 0.1);--shadow-lg:0 10px 15px -3px rgba(0, 0, 0, 0.1)}body{font-family:var(--font-body);background-color:var(--bg-deep);color:var(--text-primary);line-height:1.6;min-height:100vh;transition:background-color var(--transition-base),color var(--transition-base);background-image:radial-gradient( circle at center,rgba(30,58,138,.3) 0%,rgba(15,23,42,.5) 40%,var(--bg-deep) 100% );background-attachment:fixed;background-size:100% 100%;background-position:50%;position:relative}[data-theme=light]{--bg-deep:#ffffff}[data-theme=light] body{background-color:#fff;background-image:radial-gradient( circle at center,rgba(196,133,246,.2) 0%,rgba(243,232,255,.4) 40%,#ffffff 100% );background-attachment:fixed;background-size:100% 100%;background-position:50%}body::before{content:"";position:fixed;top:0;left:0;width:100%;height:100%;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 400 400' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");background-size:400px 400px;background-repeat:repeat;opacity:.05;pointer-events:none;z-index:0}body>*{position:relative;z-index:1} \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100755 index 0000000..68ec2ac Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon.png b/public/favicon.png new file mode 100755 index 0000000..aa75996 Binary files /dev/null and b/public/favicon.png differ diff --git a/public/favicon/favicon.ico b/public/favicon/favicon.ico new file mode 100755 index 0000000..68ec2ac Binary files /dev/null and b/public/favicon/favicon.ico differ diff --git a/public/imgs/dove.jpg b/public/imgs/dove.jpg new file mode 100755 index 0000000..aeb5498 Binary files /dev/null and b/public/imgs/dove.jpg differ diff --git a/public/imgs/email-route.jpg b/public/imgs/email-route.jpg new file mode 100755 index 0000000..cfc43c0 Binary files /dev/null and b/public/imgs/email-route.jpg differ diff --git a/public/imgs/google-group.jpg b/public/imgs/google-group.jpg new file mode 100755 index 0000000..9909dcb Binary files /dev/null and b/public/imgs/google-group.jpg differ diff --git a/public/imgs/google-spam.jpg b/public/imgs/google-spam.jpg new file mode 100755 index 0000000..9909dcb Binary files /dev/null and b/public/imgs/google-spam.jpg differ diff --git a/public/imgs/spaceship.png b/public/imgs/spaceship.png new file mode 100755 index 0000000..d51988e Binary files /dev/null and b/public/imgs/spaceship.png differ diff --git a/public/index.html b/public/index.html index 39d8f12..c6cdc62 100755 --- a/public/index.html +++ b/public/index.html @@ -1,280 +1,849 @@ - - + + + + + + + + + +Demians Blog + + + + + - - - - - -PyteDev Blog - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

Hey, I’m Demian 👋

-
-
- I am a Sysadmin, Email Enthusiast, and Open Source Contributor. I enjoy designing hosting solutions and contributing to open source projects. On this blog, I mostly write about things I’ve learned throughout my journey, as well as curious topics related to system administration, open source technology, and the web. I’m an active contributor to the ISPConfig project and am currently getting involved with KDE. -
- -
- -
-
-

Introduction -

-
-
-

Welcome to My Blog! I’m Demian, a Sysadmin, Email Infrastructure enthusiast, and a passionate Open Source contributor. This is where I’ll be sharing what I know about system administration, managing email infrastructure, and contributing to open source projects. -What to Expect On this blog, I’ll be writing about: -Sysadmin tips and tools: Everything I’ve learned managing servers, networks, and infrastructure. Email Infrastructure: Best practices for setting up, securing, and managing email systems. Open Source: How I contribute to open source projects, and tips for getting started if you want to do the same. This blog is a space to share knowledge, troubleshoot common issues, and explore new tools and techniques. Whether you’re just getting into system administration, looking for email setup guides, or interested in contributing to open source, you’ll find something useful here. -...

-
-
May 11, 2025
- -
-
+ + -
- © 2025 PyteDev Blog · + + + + + + + + + - - Powered by - Hugo & - PaperMod - -
- - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skip to main content + + +
+ + +
+
+
+
+

+ + Demians + + + Blog + +

+ +

> Mein persönlicher Blog über Open-Source Technologien, Hostinglösungen, Linux und den ganzen Rest.

+ + +
+ + +
+ +
+
+ + + + + + + +
+
+
+
+ +
+ +
+

Recent Posts

+ +
+ + + + + + + + + +
+ + + + + + + + +
+

+ Spaceship Distrobox +

+ + +

How I Solved the Distrobox Confusion in My Terminal

+

Recently, I found myself struggling with something that seemed like a simple problem: when I was …

+ + + + + + + + +
+
+ + + + + + + + + + +
+ + + + + + + + +
+

+ Google Groups Spam +

+ + +

Understanding the Google Groups Spam Problem

+

Over the past year, I’ve noticed a significant increase in spam messages originating from Google …

+ + + + + + + + +
+
+ + + + + + + + + + +
+ + + + + + + + +
+

+ Blocking Invalid Recipients Before They Reach Your Exchange Server +

+ + +

Recently, I had to deal with a serious problem: backscatter.
+One of our mail gateways ended up listed on the backscatter.org blacklist for sending …

+ + + + + + + + +
+
+ + + + + + + + + + +
+ + + + + + + + +
+

+ Dovecot Index Cache Issues +

+ + +

Understanding dovecot.index.cache

+

I recently migrated an old mail server system into its new home. After the migration, I checked the logs and noticed …

+ + + + + + + + +
+
+ + + + + + + + + + +
+ + + + + + + + +
+

+ Introduction +

+ + +

Welcome to My Blog!

+

I’m Demian, a Sysadmin, Email Infrastructure enthusiast, and a passionate Open Source contributor. This is where I’ll be sharing …

+ + + + + + + + +
+
+ + +
+
+
+ +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/public/index.json b/public/index.json new file mode 100755 index 0000000..d1a064b --- /dev/null +++ b/public/index.json @@ -0,0 +1 @@ +[{"content":"How I Solved the Distrobox Confusion in My Terminal Recently, I found myself struggling with something that seemed like a simple problem: when I was working with multiple terminal windows, I could never remember which one was inside a Distrobox and which one was on my local system. If you\u0026amp;rsquo;ve ever juggled between different environments, you know exactly how annoying this can get.\nSo, I decided to create a quick plugin for my favorite terminal prompt, Spaceship, that would clearly indicate …","date":"2026-01-04","permalink":"/posts/spaceship-distrobox/","summary":"How I Solved the Distrobox Confusion in My Terminal Recently, I found myself struggling with something that seemed like a simple problem: when I was working with multiple terminal windows, I could …","tags":null,"title":"Spaceship Distrobox"},{"content":"Understanding the Google Groups Spam Problem Over the past year, I\u0026amp;rsquo;ve noticed a significant increase in spam messages originating from Google Groups. This issue stems from the way Google Groups is designed: It gives spammers an easy way to distribute large volumes of unwanted mail using legitimate Google mail servers, which makes filtering much harder.\nWhy it Happens There are a few fundamental problems with how Google Groups works that make it particularly attractive to spammers:\nNo …","date":"2025-10-10","permalink":"/posts/google-groups-spam/","summary":"Understanding the Google Groups Spam Problem Over the past year, I\u0026rsquo;ve noticed a significant increase in spam messages originating from Google Groups. This issue stems from the way Google Groups …","tags":null,"title":"Google Groups Spam"},{"content":"Recently, I had to deal with a serious problem: backscatter.\nOne of our mail gateways ended up listed on the backscatter.org blacklist for sending bounce messages to forged senders.\nAfter checking the logs, I quickly realized that our system wasn\u0026amp;rsquo;t actually protected against backscatter attacks, so I had to do something about it.\nWhat Even Is Backscatter? Backscatter is unwanted email that your mail server sends after receiving a message, usually in the form of a non-delivery report (NDR) …","date":"2025-08-01","permalink":"/posts/blocking-invalid-rcpt-postfix/","summary":"Recently, I had to deal with a serious problem: backscatter.\nOne of our mail gateways ended up listed on the backscatter.org blacklist for sending bounce messages to forged senders.\nAfter checking the …","tags":null,"title":"Blocking Invalid Recipients Before They Reach Your Exchange Server"},{"content":"Understanding dovecot.index.cache I recently migrated an old mail server system into its new home. After the migration, I checked the logs and noticed some warnings that looked like this:\nMay 17 11:23:13 server1 dovecot: dsync-local(user@domain.tld)\u0026amp;lt;cRjZCwGnKWiIvicA2dm5Tw\u0026amp;gt;: Error: Mailbox INBOX: mmap(size=511310568) failed with file /var/vmail/domain.tld/user/Maildir/dovecot.index.cache: Cannot allocate memory The error indicates that the dovecot.index.cache file is too big to process, and …","date":"2025-05-18","permalink":"/posts/dovecot-index-cache-issues/","summary":"Understanding dovecot.index.cache I recently migrated an old mail server system into its new home. After the migration, I checked the logs and noticed some warnings that looked like this:\nMay 17 …","tags":null,"title":"Dovecot Index Cache Issues"},{"content":"Welcome to My Blog! I’m Demian, a Sysadmin, Email Infrastructure enthusiast, and a passionate Open Source contributor. This is where I’ll be sharing what I know about system administration, managing email infrastructure, and contributing to open source projects.\nWhat to Expect On this blog, I’ll be writing about:\nSysadmin tips and tools: Everything I’ve learned managing servers, networks, and infrastructure. Email Infrastructure: Best practices for setting up, securing, and managing email …","date":"2025-05-11","permalink":"/posts/my-first-post/","summary":"Welcome to My Blog! I’m Demian, a Sysadmin, Email Infrastructure enthusiast, and a passionate Open Source contributor. This is where I’ll be sharing what I know about system administration, managing …","tags":null,"title":"Introduction"}] \ No newline at end of file diff --git a/public/index.xml b/public/index.xml index 2ce496a..fa12ac6 100755 --- a/public/index.xml +++ b/public/index.xml @@ -1,35 +1,54 @@ - + - PyteDev Blog + Demian's Blog https://pyte.dev/ - Recent content on PyteDev Blog - Hugo -- 0.147.2 + Recent content on Demian's Blog + Hugo en-us - Sun, 11 May 2025 20:13:49 +0200 + Sun, 04 Jan 2026 20:33:45 +0100 + + Spaceship Distrobox + https://pyte.dev/posts/spaceship-distrobox/ + Sun, 04 Jan 2026 20:33:45 +0100 + https://pyte.dev/posts/spaceship-distrobox/ + <h1 id="how-i-solved-the-distrobox-confusion-in-my-terminal">How I Solved the Distrobox Confusion in My Terminal</h1> <p>Recently, I found myself struggling with something that seemed like a simple problem: when I was working with multiple terminal windows, I could never remember which one was inside a Distrobox and which one was on my local system. If you&rsquo;ve ever juggled between different environments, you know exactly how annoying this can get.</p> <p>So, I decided to create a quick plugin for my favorite terminal prompt, <strong>Spaceship</strong>, that would clearly indicate when I&rsquo;m inside a Distrobox container. The result is a simple, customizable section that appears right in your prompt, showing the name of the active container. This way, I never have to guess or dig deeper to figure out where I&rsquo;m working.</p> + + + Google Groups Spam + https://pyte.dev/posts/google-groups-spam/ + Fri, 10 Oct 2025 09:26:56 +0200 + https://pyte.dev/posts/google-groups-spam/ + <h1 id="understanding-the-google-groups-spam-problem">Understanding the Google Groups Spam Problem</h1> <p>Over the past year, I&rsquo;ve noticed a significant increase in spam messages originating from Google Groups. This issue stems from the way Google Groups is designed: It gives spammers an easy way to distribute large volumes of unwanted mail using legitimate Google mail servers, which makes filtering much harder.</p> <h2 id="why-it-happens">Why it Happens</h2> <p>There are a few fundamental problems with how Google Groups works that make it particularly attractive to spammers:</p> + + + Blocking Invalid Recipients Before They Reach Your Exchange Server + https://pyte.dev/posts/blocking-invalid-rcpt-postfix/ + Fri, 01 Aug 2025 10:03:15 +0200 + https://pyte.dev/posts/blocking-invalid-rcpt-postfix/ + <p>Recently, I had to deal with a serious problem: <strong>backscatter</strong>.<br> One of our mail gateways ended up listed on the <strong>backscatter.org</strong> blacklist for sending bounce messages to forged senders.</p> <p>After checking the logs, I quickly realized that our system wasn&rsquo;t actually protected against backscatter attacks, so I had to do something about it.</p> <h2 id="what-even-is-backscatter">What Even Is Backscatter?</h2> <p>Backscatter is unwanted email that your mail server sends <strong>after</strong> receiving a message, usually in the form of a <strong>non-delivery report (NDR)</strong> or <strong>bounce</strong> to a <strong>forged sender address</strong>.</p> + + + Dovecot Index Cache Issues + https://pyte.dev/posts/dovecot-index-cache-issues/ + Sun, 18 May 2025 11:34:09 +0200 + https://pyte.dev/posts/dovecot-index-cache-issues/ + <h1 id="understanding-dovecotindexcache">Understanding <code>dovecot.index.cache</code></h1> <p>I recently migrated an old mail server system into its new home. After the migration, I checked the logs and noticed some warnings that looked like this:</p> <pre tabindex="0"><code>May 17 11:23:13 server1 dovecot: dsync-local(user@domain.tld)&lt;cRjZCwGnKWiIvicA2dm5Tw&gt;: Error: Mailbox INBOX: mmap(size=511310568) failed with file /var/vmail/domain.tld/user/Maildir/dovecot.index.cache: Cannot allocate memory </code></pre><p>The error indicates that the <code>dovecot.index.cache</code> file is too big to process, and Dovecot cannot allocate enough memory to handle it.</p> <h1 id="what-are-dovecotindexcache-files">What Are <code>dovecot.index.cache</code> Files?</h1> <p>Dovecot, the most popular IMAP server, uses a set of index files (<code>dovecot.index</code>, <code>dovecot.index.cache</code>, <code>dovecot.index.log</code>, etc.) to speed up mailbox access. The file I had to deal with stores cached message metadata (headers, flags, and preview text) so Dovecot does not have to read each message file in the mailbox every time.</p> + Introduction https://pyte.dev/posts/my-first-post/ Sun, 11 May 2025 20:13:49 +0200 https://pyte.dev/posts/my-first-post/ - <h1 id="welcome-to-my-blog">Welcome to My Blog!</h1> -<p>I’m <strong>Demian</strong>, a <strong>Sysadmin</strong>, <strong>Email Infrastructure enthusiast</strong>, and a passionate <strong>Open Source contributor</strong>. This is where I’ll be sharing what I know about system administration, managing email infrastructure, and contributing to open source projects.</p> -<h2 id="what-to-expect">What to Expect</h2> -<p>On this blog, I’ll be writing about:</p> -<ul> -<li><strong>Sysadmin tips and tools</strong>: Everything I’ve learned managing servers, networks, and infrastructure.</li> -<li><strong>Email Infrastructure</strong>: Best practices for setting up, securing, and managing email systems.</li> -<li><strong>Open Source</strong>: How I contribute to open source projects, and tips for getting started if you want to do the same.</li> -</ul> -<p>This blog is a space to share knowledge, troubleshoot common issues, and explore new tools and techniques. Whether you’re just getting into system administration, looking for email setup guides, or interested in contributing to open source, you’ll find something useful here.</p> + <h1 id="welcome-to-my-blog">Welcome to My Blog!</h1> <p>I’m <strong>Demian</strong>, a <strong>Sysadmin</strong>, <strong>Email Infrastructure enthusiast</strong>, and a passionate <strong>Open Source contributor</strong>. This is where I’ll be sharing what I know about system administration, managing email infrastructure, and contributing to open source projects.</p> <h2 id="what-to-expect">What to Expect</h2> <p>On this blog, I’ll be writing about:</p> <ul> <li><strong>Sysadmin tips and tools</strong>: Everything I’ve learned managing servers, networks, and infrastructure.</li> <li><strong>Email Infrastructure</strong>: Best practices for setting up, securing, and managing email systems.</li> <li><strong>Open Source</strong>: How I contribute to open source projects, and tips for getting started if you want to do the same.</li> </ul> <p>This blog is a space to share knowledge, troubleshoot common issues, and explore new tools and techniques. Whether you’re just getting into system administration, looking for email setup guides, or interested in contributing to open source, you’ll find something useful here.</p> Search https://pyte.dev/search/ Mon, 01 Jan 0001 00:00:00 +0000 https://pyte.dev/search/ - Search for a specific post or topic + <link href="https://pyte.dev/pagefind/pagefind-ui.css" rel="stylesheet"> <script src="https://pyte.dev/pagefind/pagefind-ui.js"></script> <div id="search"></div> <script> window.addEventListener('DOMContentLoaded', (event) => { new PagefindUI({ element: "#search", showSubResults: true }); }); </script> diff --git a/public/js/main.min.e60ab79dca7b920b4dc5cf3163ad5ce8794839b60f27778db65782f087be3e27.js b/public/js/main.min.e60ab79dca7b920b4dc5cf3163ad5ce8794839b60f27778db65782f087be3e27.js new file mode 100755 index 0000000..d7658e2 --- /dev/null +++ b/public/js/main.min.e60ab79dca7b920b4dc5cf3163ad5ce8794839b60f27778db65782f087be3e27.js @@ -0,0 +1,29 @@ +const MIN_SEARCH_QUERY_LENGTH=2,SEARCH_RESULTS_LIMIT=10,SEARCH_DEBOUNCE_DELAY=200,SEARCH_INDEX_URL="/index.json",MODAL_FOCUS_DELAY=100,SCROLL_THRESHOLD=300,WIDE_SCREEN_BREAKPOINT=1024,MOBILE_BREAKPOINT=768,TOC_SCROLL_OFFSET=100,TOC_RESIZE_DEBOUNCE=150,DEBOUNCE_DELAY=200;function debounce(e,t){let n;return function(...s){const o=()=>{clearTimeout(n),e(...s)};clearTimeout(n),n=setTimeout(o,t)}}function throttle(e){let t=!1;return function(...n){t||(window.requestAnimationFrame(()=>{e(...n),t=!1}),t=!0)}}function isWideScreen(){return window.innerWidth>WIDE_SCREEN_BREAKPOINT}function isMobileScreen(){return window.innerWidth<=MOBILE_BREAKPOINT}function escapeRegex(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function formatDate(e){if(!e)return"";try{const t=new Date(e),n=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];return`${n[t.getMonth()]} ${t.getDate()}, ${t.getFullYear()}`}catch(t){return console.error("Error formatting date:",t),e}}function initOnReady(e){document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e):e()}const THEME_STORAGE_KEY="theme",THEME_LIGHT="light",THEME_DARK="dark";function getInitialTheme(){const e=localStorage.getItem(THEME_STORAGE_KEY);if(e)return e;const t=window.matchMedia("(prefers-color-scheme: light)").matches;return t?THEME_LIGHT:THEME_DARK}function setTheme(e){const t=document.documentElement;t.setAttribute("data-theme",e),localStorage.setItem(THEME_STORAGE_KEY,e),updateThemeToggleIcon(e)}function updateThemeToggleIcon(e){const t=document.getElementById("theme-toggle");if(!t)return;const n=t.querySelector(".icon-sun"),s=t.querySelector(".icon-moon");e===THEME_LIGHT?(n?.setAttribute("style","display: none;"),s?.setAttribute("style","display: block;")):(n?.setAttribute("style","display: block;"),s?.setAttribute("style","display: none;"))}function toggleTheme(){const e=document.documentElement,t=e.getAttribute("data-theme"),n=t===THEME_LIGHT?THEME_DARK:THEME_LIGHT;setTheme(n)}function initThemeToggle(){const e=document.getElementById("theme-toggle");if(!e)return;const t=getInitialTheme();setTheme(t),window.matchMedia("(prefers-color-scheme: light)").addEventListener("change",e=>{localStorage.getItem(THEME_STORAGE_KEY)||setTheme(e.matches?THEME_LIGHT:THEME_DARK)}),e.addEventListener("click",toggleTheme)}initOnReady(initThemeToggle);let searchIndex=[],searchTimeout=null;async function loadSearchIndex(){try{const e=await fetch(SEARCH_INDEX_URL);e.ok?searchIndex=await e.json():console.error("Failed to load search index:",e.status)}catch(e){console.error("Failed to load search index:",e)}}function toggleSearchResultsVisibility(){const e=document.getElementById("search-results");if(!e)return;const t=e.innerHTML.trim().length>0;e.style.display=t?"block":"none"}function highlightMatch(e,t){if(!e||!t)return e;const s=t.endsWith(" "),n=t.trim();if(!n)return e;const o=escapeRegex(n),i=new RegExp(`(${o})`,"gi");return e.replace(i,e=>s?`${e} `:`${e}`)}function performSearch(e){const t=document.getElementById("search-results");if(!t)return;const s=e?e.trim():"";if(!s||s.length{if(!e||!e.permalink)return!1;if(o.has(e.permalink))return!1;o.add(e.permalink);const t=e.title?.toLowerCase().includes(n),s=e.summary?.toLowerCase().includes(n),i=e.content?.toLowerCase().includes(n),a=e.tags?.some(e=>e.toLowerCase().includes(n));return t||s||i||a}).slice(0,SEARCH_RESULTS_LIMIT);if(i.length===0){t.innerHTML='
No results found
',toggleSearchResultsVisibility();return}t.innerHTML=i.map(t=>` + +
${highlightMatch(t.title||"",e)}
+ ${t.summary?`
${highlightMatch(t.summary.substring(0,150),e)}...
`:""} + ${t.date?`
+ + + + ${formatDate(t.date)} +
`:""} +
+ `).join(""),toggleSearchResultsVisibility()}function toggleClearButton(){const e=document.getElementById("search-input-clear"),t=document.getElementById("search-input");if(!e||!t)return;t.value.length>0?e.classList.add("visible"):e.classList.remove("visible")}function openSearchModal(){const e=document.getElementById("search-modal");if(!e)return;e.setAttribute("aria-hidden","false"),document.body.style.overflow="hidden",setTimeout(()=>{const e=document.getElementById("search-input");e?.focus(),toggleClearButton()},MODAL_FOCUS_DELAY)}function closeSearchModal(){const t=document.getElementById("search-modal");if(!t)return;t.setAttribute("aria-hidden","true"),document.body.style.overflow="";const n=document.getElementById("search-input"),e=document.getElementById("search-results");n&&(n.value="",toggleClearButton()),e&&(e.innerHTML="",e.style.display="none")}function setupSearchInput(){const e=document.getElementById("search-input");if(!e)return;const t=debounce(e=>{performSearch(e)},SEARCH_DEBOUNCE_DELAY);e.addEventListener("input",e=>{const n=e.target.value;toggleClearButton(),t(n)}),toggleClearButton()}function setupClearButton(){const e=document.getElementById("search-input-clear");if(!e)return;e.addEventListener("click",e=>{e.preventDefault(),e.stopPropagation();const t=document.getElementById("search-input"),n=document.getElementById("search-results");t&&(t.value="",t.focus(),toggleClearButton(),n&&(n.innerHTML="",n.style.display="none"),t.dispatchEvent(new Event("input",{bubbles:!0})))})}function setupKeyboardShortcuts(){document.addEventListener("keydown",e=>{const t=document.getElementById("search-modal");e.key==="Escape"&&t?.getAttribute("aria-hidden")==="false"&&closeSearchModal()})}function setupModalClickHandling(){const e=document.getElementById("search-modal"),t=e?.querySelector(".search-modal-container");t?.addEventListener("click",e=>{e.stopPropagation()})}function initSearch(){const e=document.getElementById("search-toggle"),t=document.getElementById("search-modal-backdrop"),n=document.getElementById("search-modal-close");e&&e.addEventListener("click",openSearchModal),t&&t.addEventListener("click",closeSearchModal),n&&n.addEventListener("click",closeSearchModal),setupSearchInput(),setupClearButton(),setupKeyboardShortcuts(),setupModalClickHandling(),loadSearchIndex()}(function(){document.readyState==="loading"?document.addEventListener("DOMContentLoaded",initSearch):initSearch()})();function toggleScrollButton(){const e=document.getElementById("scroll-to-top");if(!e)return;window.scrollY>SCROLL_THRESHOLD?e.classList.add("visible"):e.classList.remove("visible")}function scrollToTop(){window.scrollTo({top:0,behavior:"smooth"})}function initScrollToTop(){const e=document.getElementById("scroll-to-top");if(!e)return;window.addEventListener("scroll",toggleScrollButton),e.addEventListener("click",scrollToTop)}(function(){document.readyState==="loading"?document.addEventListener("DOMContentLoaded",initScrollToTop):initScrollToTop()})();function initGlassDockNavigation(){const t=document.querySelector(".glass-dock-toggle"),e=document.querySelector(".glass-dock-list");if(!t||!e)return;t.addEventListener("click",function(){const t=this.getAttribute("aria-expanded")==="true";this.setAttribute("aria-expanded",!t),e.classList.toggle("mobile-open",!t)}),e.querySelectorAll(".glass-dock-link").forEach(n=>{n.addEventListener("click",function(){window.innerWidth<=MOBILE_BREAKPOINT&&(t.setAttribute("aria-expanded","false"),e.classList.remove("mobile-open"))})}),document.addEventListener("click",function(n){window.innerWidth<=MOBILE_BREAKPOINT&&!t.contains(n.target)&&!e.contains(n.target)&&(t.setAttribute("aria-expanded","false"),e.classList.remove("mobile-open"))})}function initSmoothScroll(){document.querySelectorAll('a[href^="#"]').forEach(e=>{e.addEventListener("click",function(e){const t=this.getAttribute("href");if(t==="#"||t.length<=1)return;const n=document.querySelector(t);n&&(e.preventDefault(),n.scrollIntoView({behavior:"smooth",block:"start"}))})})}function initPostCardClick(){document.querySelectorAll(".post-card[data-post-url]").forEach(e=>{e.addEventListener("click",function(e){if(e.target.closest("a"))return;const t=this.getAttribute("data-post-url");t&&(window.location.href=t)})})}function initNavigation(){initGlassDockNavigation(),initSmoothScroll(),initPostCardClick()}(function(){document.readyState==="loading"?document.addEventListener("DOMContentLoaded",initNavigation):initNavigation()})();function isWideScreen(){return window.innerWidth>WIDE_SCREEN_BREAKPOINT}function expandTOC(e,t){e.setAttribute("aria-expanded","true"),t.classList.add("expanded")}function collapseTOC(e,t){e.setAttribute("aria-expanded","false"),t.classList.remove("expanded")}function toggleTOC(e,t){const n=e.getAttribute("aria-expanded")==="true";n?collapseTOC(e,t):expandTOC(e,t)}function initializeTOCState(e,t){isWideScreen()?expandTOC(e,t):collapseTOC(e,t)}function updateActiveTOCItem(e,t,n){const o=window.scrollY+TOC_SCROLL_OFFSET;let s=null;for(let t=e.length-1;t>=0;t--){const{element:n}=e[t];if(n&&n.offsetTop<=o){s=e[t];break}}if(t.forEach(e=>e.classList.remove("active")),s&&(s.link.classList.add("active"),n.classList.contains("expanded")&&s.link)){const e=s.link.offsetTop,t=s.link.offsetHeight,o=n.offsetHeight,i=n.scrollTop;ei+o&&n.scrollTo({top:e-o+t+20,behavior:"smooth"})}}function initTOC(){const i=document.getElementById("post-toc"),t=document.getElementById("toc-toggle"),e=document.getElementById("toc-content");if(!i||!t||!e)return;const n=i.querySelectorAll('a[href^="#"]'),s=Array.from(n).map(e=>{const n=e.getAttribute("href"),t=n.substring(1),s=document.getElementById(t);return{link:e,element:s,id:t}}).filter(e=>e.element);t.addEventListener("click",()=>{toggleTOC(t,e)});let a;if(window.addEventListener("resize",()=>{clearTimeout(a),a=setTimeout(()=>{initializeTOCState(t,e)},TOC_RESIZE_DEBOUNCE)}),initializeTOCState(t,e),s.length===0)return;let o=!1;window.addEventListener("scroll",()=>{o||(window.requestAnimationFrame(()=>{updateActiveTOCItem(s,n,e),o=!1}),o=!0)}),updateActiveTOCItem(s,n,e)}(function(){document.readyState==="loading"?document.addEventListener("DOMContentLoaded",initTOC):initTOC()})();function initFilters(){const r=document.getElementById("filter-year-month"),j=document.getElementById("clear-filters"),h=document.getElementById("posts-container"),f=document.getElementById("filtered-count"),n=document.getElementById("tag-search-input"),t=document.getElementById("tag-search-results"),l=document.getElementById("selected-tags-container"),b=document.getElementById("all-tags-data");if(!h)return;const m=Array.from(b?.querySelectorAll("[data-tag]")||[]).map(e=>e.getAttribute("data-tag")),o=new Set;let e=-1;function p(e){const t=document.createElement("label");return t.className="filter-tag-checkbox",t.innerHTML=` + + ${e} + `,t}function c(e){if(o.has(e))return;o.add(e);const t=p(e);t.querySelector(".filter-tag-input").addEventListener("change",function(){this.checked||g(e),a()}),l.appendChild(t),a()}function g(e){o.delete(e);const t=l.querySelector(`input[value="${e}"]`)?.closest(".filter-tag-checkbox");t&&t.remove(),a()}function u(s){if(!t)return;const i=s.toLowerCase().trim();if(!i){t.innerHTML="",t.style.display="none",e=-1;return}const a=m.filter(e=>e.toLowerCase().includes(i)&&!o.has(e));if(a.length===0){t.innerHTML='
No tags found
',t.style.display="block",e=-1;return}t.innerHTML=a.map((t,n)=>`
${t}
`).join(""),t.style.display="block",t.querySelectorAll(".tag-search-result-item").forEach(s=>{s.addEventListener("click",()=>{const o=s.getAttribute("data-tag");c(o),n.value="",t.style.display="none",e=-1,n.focus()})}),d()}function d(){const n=t.querySelectorAll(".tag-search-result-item");n.forEach((t,n)=>{n===e?t.classList.add("selected"):t.classList.remove("selected")})}function v(){const s=t.querySelectorAll(".tag-search-result-item");if(s.length===0){const i=n.value.trim(),s=m.find(e=>e.toLowerCase()===i.toLowerCase()&&!o.has(e));if(s){c(s),n.value="",t.style.display="none",e=-1;return}return}if(e>=0&&e0){const o=s[0],i=o.getAttribute("data-tag");c(i),n.value="",t.style.display="none",e=-1}}function a(){const e=r?.value||"",t=Array.from(o),s=h.querySelectorAll(".post-card");let n=0;s.forEach(s=>{const o=s.getAttribute("data-year-month")||"",i=s.getAttribute("data-tags")?.split(",")||[],a=!e||o===e,r=t.length===0||t.some(e=>i.includes(e));a&&r?(s.style.display="",n++):s.style.display="none"}),f&&(f.textContent=n)}r?.addEventListener("change",a),n&&(n.addEventListener("input",t=>{e=-1,u(t.target.value)}),n.addEventListener("focus",()=>{n.value.trim()&&u(n.value)}),n.addEventListener("keydown",s=>{const o=t.querySelectorAll(".tag-search-result-item");s.key==="ArrowDown"?(s.preventDefault(),o.length>0&&(e=e0&&(e=e>0?e-1:o.length-1,d(),o[e]&&o[e].scrollIntoView({block:"nearest",behavior:"smooth"}))):s.key==="Enter"?(s.preventDefault(),v()):s.key==="Escape"&&(t.style.display="none",e=-1,n.blur())}),document.addEventListener("click",s=>{!n.contains(s.target)&&!t.contains(s.target)&&(t.style.display="none",e=-1)})),j?.addEventListener("click",()=>{r&&(r.value=""),o.clear(),l.innerHTML="",n&&(n.value=""),t&&(t.style.display="none"),a()});const i=document.getElementById("filter-toggle-btn"),s=document.getElementById("posts-filter-content");i&&s&&(s.style.display="flex",i.addEventListener("click",()=>{const e=i.getAttribute("aria-expanded")==="true";if(e)s.style.maxHeight=s.scrollHeight+"px",s.offsetHeight,s.style.maxHeight="0px",s.classList.remove("expanded");else{s.style.maxHeight="none";const e=s.scrollHeight;s.style.maxHeight="0px",s.offsetHeight,s.style.maxHeight=Math.max(e,300)+"px",s.classList.add("expanded")}i.setAttribute("aria-expanded",!e),i.classList.toggle("expanded",!e)}))}(function(){document.readyState==="loading"?document.addEventListener("DOMContentLoaded",initFilters):initFilters()})();function initCollapsibleCodeBlocks(){const e=document.querySelector(".post-content-main");if(!e)return;const t=e.querySelectorAll(".highlight"),n=Array.from(e.querySelectorAll("pre")).filter(e=>!e.closest(".highlight")),s=[...t,...n];s.forEach(e=>{let n=e.closest(".code-block-wrapper"),s=!1;n||(n=document.createElement("div"),n.className="code-block-wrapper collapsed",s=!0);let t=n.querySelector(".code-block-copy");if(!t){t=document.createElement("button"),t.className="code-block-copy",t.setAttribute("aria-label","Copy code"),t.innerHTML=` + + + + + Copy + + `;const o=()=>{const t=e.querySelector("code")||e;return t.textContent||t.innerText||""};t.addEventListener("click",async e=>{e.stopPropagation();const s=o(),n=window.innerWidth<=768;try{await navigator.clipboard.writeText(s);const e=t.querySelector(".copy-text"),o=t.querySelector(".copied-text"),i=t.querySelector(".copy-icon"),a=t.querySelector(".checkmark-icon");t.setAttribute("aria-label","Code copied"),i.style.display="none",a.style.display="block",n||(e.style.display="none",o.style.display="inline"),t.classList.add("copied"),t.setAttribute("aria-label","Code copied"),setTimeout(()=>{a.style.display="none",i.style.display="block",n||(o.style.display="none",e.style.display="inline"),t.classList.remove("copied"),t.setAttribute("aria-label","Copy code")},2e3)}catch{const e=document.createElement("textarea");e.value=s,e.style.position="fixed",e.style.opacity="0",document.body.appendChild(e),e.select();try{document.execCommand("copy");const e=t.querySelector(".copy-text"),s=t.querySelector(".copied-text"),o=t.querySelector(".copy-icon"),i=t.querySelector(".checkmark-icon");o.style.display="none",i.style.display="block",n||(e.style.display="none",s.style.display="inline"),t.classList.add("copied"),t.setAttribute("aria-label","Code copied"),setTimeout(()=>{o.style.display="block",i.style.display="none",n||(e.style.display="inline",s.style.display="none"),t.classList.remove("copied"),t.setAttribute("aria-label","Copy code")},2e3)}catch(e){console.error("Failed to copy code:",e)}document.body.removeChild(e)}});const s=n.querySelector(".code-block-toggle");s?n.insertBefore(t,s):n.insertBefore(t,n.firstChild)}if(s){const t=document.createElement("button");t.className="code-block-toggle",t.setAttribute("aria-label","Toggle code block"),t.setAttribute("aria-expanded","false"),t.innerHTML=` + Expand + + + + `;const s=document.createElement("div");s.className="code-block-content",e.parentNode.insertBefore(n,e),n.appendChild(t),n.appendChild(s),s.appendChild(e),t.addEventListener("click",()=>{const e=n.classList.contains("collapsed");n.classList.toggle("collapsed"),t.setAttribute("aria-expanded",!e),t.querySelector("span").textContent=e?"Collapse":"Expand"})}})}(function(){document.readyState==="loading"?document.addEventListener("DOMContentLoaded",initCollapsibleCodeBlocks):initCollapsibleCodeBlocks()})(),function(){"use strict";function e(){const e=document.querySelectorAll(".alert-collapsible .alert-header");e.forEach(e=>{if(e.dataset.initialized==="true")return;e.dataset.initialized="true",e.addEventListener("click",function(){const e=this.closest(".alert-collapsible"),t=e.querySelector(".alert-content"),n=e.querySelector(".alert-toggle");if(!t||!n)return;const s=t.style.display==="none"||e.dataset.collapsed==="true";s?(t.style.display="block",n.textContent="−",e.dataset.collapsed="false"):(t.style.display="none",n.textContent="+",e.dataset.collapsed="true")})})}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e):e(),typeof window!="undefined"&&(window.initAlertToggles=e)}() \ No newline at end of file diff --git a/public/pagefind/fragment/de-de_11692cd.pf_fragment b/public/pagefind/fragment/de-de_11692cd.pf_fragment new file mode 100755 index 0000000..ac09239 Binary files /dev/null and b/public/pagefind/fragment/de-de_11692cd.pf_fragment differ diff --git a/public/pagefind/fragment/de-de_316eabe.pf_fragment b/public/pagefind/fragment/de-de_316eabe.pf_fragment new file mode 100755 index 0000000..c3acf80 Binary files /dev/null and b/public/pagefind/fragment/de-de_316eabe.pf_fragment differ diff --git a/public/pagefind/fragment/de-de_368af57.pf_fragment b/public/pagefind/fragment/de-de_368af57.pf_fragment new file mode 100755 index 0000000..e5ec5f6 Binary files /dev/null and b/public/pagefind/fragment/de-de_368af57.pf_fragment differ diff --git a/public/pagefind/fragment/de-de_5a668b4.pf_fragment b/public/pagefind/fragment/de-de_5a668b4.pf_fragment new file mode 100755 index 0000000..09c8237 Binary files /dev/null and b/public/pagefind/fragment/de-de_5a668b4.pf_fragment differ diff --git a/public/pagefind/fragment/de-de_6ad6ed3.pf_fragment b/public/pagefind/fragment/de-de_6ad6ed3.pf_fragment new file mode 100755 index 0000000..33df5b0 Binary files /dev/null and b/public/pagefind/fragment/de-de_6ad6ed3.pf_fragment differ diff --git a/public/pagefind/fragment/de-de_6ec4905.pf_fragment b/public/pagefind/fragment/de-de_6ec4905.pf_fragment new file mode 100755 index 0000000..16736d3 Binary files /dev/null and b/public/pagefind/fragment/de-de_6ec4905.pf_fragment differ diff --git a/public/pagefind/fragment/de-de_bc89f5a.pf_fragment b/public/pagefind/fragment/de-de_bc89f5a.pf_fragment new file mode 100755 index 0000000..f857aa2 Binary files /dev/null and b/public/pagefind/fragment/de-de_bc89f5a.pf_fragment differ diff --git a/public/pagefind/fragment/de-de_cb74f1b.pf_fragment b/public/pagefind/fragment/de-de_cb74f1b.pf_fragment new file mode 100755 index 0000000..d3df9d1 Binary files /dev/null and b/public/pagefind/fragment/de-de_cb74f1b.pf_fragment differ diff --git a/public/pagefind/fragment/de-de_cd93b44.pf_fragment b/public/pagefind/fragment/de-de_cd93b44.pf_fragment new file mode 100755 index 0000000..6d46398 Binary files /dev/null and b/public/pagefind/fragment/de-de_cd93b44.pf_fragment differ diff --git a/public/pagefind/fragment/de-de_ec998a5.pf_fragment b/public/pagefind/fragment/de-de_ec998a5.pf_fragment new file mode 100755 index 0000000..b01b6c0 Binary files /dev/null and b/public/pagefind/fragment/de-de_ec998a5.pf_fragment differ diff --git a/public/pagefind/fragment/de-de_f1d8ca7.pf_fragment b/public/pagefind/fragment/de-de_f1d8ca7.pf_fragment new file mode 100755 index 0000000..5995e13 Binary files /dev/null and b/public/pagefind/fragment/de-de_f1d8ca7.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_26dac37.pf_fragment b/public/pagefind/fragment/en-us_26dac37.pf_fragment new file mode 100755 index 0000000..c6970c1 Binary files /dev/null and b/public/pagefind/fragment/en-us_26dac37.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_316eabe.pf_fragment b/public/pagefind/fragment/en-us_316eabe.pf_fragment new file mode 100755 index 0000000..c3acf80 Binary files /dev/null and b/public/pagefind/fragment/en-us_316eabe.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_368af57.pf_fragment b/public/pagefind/fragment/en-us_368af57.pf_fragment new file mode 100755 index 0000000..e5ec5f6 Binary files /dev/null and b/public/pagefind/fragment/en-us_368af57.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_40b7349.pf_fragment b/public/pagefind/fragment/en-us_40b7349.pf_fragment new file mode 100755 index 0000000..6968578 Binary files /dev/null and b/public/pagefind/fragment/en-us_40b7349.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_489961d.pf_fragment b/public/pagefind/fragment/en-us_489961d.pf_fragment new file mode 100755 index 0000000..cc9c781 Binary files /dev/null and b/public/pagefind/fragment/en-us_489961d.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_5a668b4.pf_fragment b/public/pagefind/fragment/en-us_5a668b4.pf_fragment new file mode 100755 index 0000000..09c8237 Binary files /dev/null and b/public/pagefind/fragment/en-us_5a668b4.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_6ec4905.pf_fragment b/public/pagefind/fragment/en-us_6ec4905.pf_fragment new file mode 100755 index 0000000..16736d3 Binary files /dev/null and b/public/pagefind/fragment/en-us_6ec4905.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_70145fc.pf_fragment b/public/pagefind/fragment/en-us_70145fc.pf_fragment new file mode 100755 index 0000000..36b9462 Binary files /dev/null and b/public/pagefind/fragment/en-us_70145fc.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_b788984.pf_fragment b/public/pagefind/fragment/en-us_b788984.pf_fragment new file mode 100755 index 0000000..f8756e0 Binary files /dev/null and b/public/pagefind/fragment/en-us_b788984.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_bc89f5a.pf_fragment b/public/pagefind/fragment/en-us_bc89f5a.pf_fragment new file mode 100755 index 0000000..f857aa2 Binary files /dev/null and b/public/pagefind/fragment/en-us_bc89f5a.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_c1cf2f4.pf_fragment b/public/pagefind/fragment/en-us_c1cf2f4.pf_fragment new file mode 100755 index 0000000..050336f Binary files /dev/null and b/public/pagefind/fragment/en-us_c1cf2f4.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_cb74f1b.pf_fragment b/public/pagefind/fragment/en-us_cb74f1b.pf_fragment new file mode 100755 index 0000000..d3df9d1 Binary files /dev/null and b/public/pagefind/fragment/en-us_cb74f1b.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_cd93b44.pf_fragment b/public/pagefind/fragment/en-us_cd93b44.pf_fragment new file mode 100755 index 0000000..6d46398 Binary files /dev/null and b/public/pagefind/fragment/en-us_cd93b44.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_ec998a5.pf_fragment b/public/pagefind/fragment/en-us_ec998a5.pf_fragment new file mode 100755 index 0000000..b01b6c0 Binary files /dev/null and b/public/pagefind/fragment/en-us_ec998a5.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_ed817f6.pf_fragment b/public/pagefind/fragment/en-us_ed817f6.pf_fragment new file mode 100755 index 0000000..fde9a4a Binary files /dev/null and b/public/pagefind/fragment/en-us_ed817f6.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_f1d8ca7.pf_fragment b/public/pagefind/fragment/en-us_f1d8ca7.pf_fragment new file mode 100755 index 0000000..5995e13 Binary files /dev/null and b/public/pagefind/fragment/en-us_f1d8ca7.pf_fragment differ diff --git a/public/pagefind/fragment/en-us_fc59e25.pf_fragment b/public/pagefind/fragment/en-us_fc59e25.pf_fragment new file mode 100755 index 0000000..f17b66e Binary files /dev/null and b/public/pagefind/fragment/en-us_fc59e25.pf_fragment differ diff --git a/public/pagefind/fragment/en_1d89322.pf_fragment b/public/pagefind/fragment/en_1d89322.pf_fragment new file mode 100755 index 0000000..3a4de51 Binary files /dev/null and b/public/pagefind/fragment/en_1d89322.pf_fragment differ diff --git a/public/pagefind/fragment/en_26ad22e.pf_fragment b/public/pagefind/fragment/en_26ad22e.pf_fragment new file mode 100755 index 0000000..e3e9d27 Binary files /dev/null and b/public/pagefind/fragment/en_26ad22e.pf_fragment differ diff --git a/public/pagefind/fragment/en_2748314.pf_fragment b/public/pagefind/fragment/en_2748314.pf_fragment new file mode 100755 index 0000000..0237a87 Binary files /dev/null and b/public/pagefind/fragment/en_2748314.pf_fragment differ diff --git a/public/pagefind/fragment/en_2c0c5bc.pf_fragment b/public/pagefind/fragment/en_2c0c5bc.pf_fragment new file mode 100755 index 0000000..10285fe Binary files /dev/null and b/public/pagefind/fragment/en_2c0c5bc.pf_fragment differ diff --git a/public/pagefind/fragment/en_4772d61.pf_fragment b/public/pagefind/fragment/en_4772d61.pf_fragment new file mode 100755 index 0000000..ce37be7 Binary files /dev/null and b/public/pagefind/fragment/en_4772d61.pf_fragment differ diff --git a/public/pagefind/fragment/en_4da380c.pf_fragment b/public/pagefind/fragment/en_4da380c.pf_fragment new file mode 100755 index 0000000..ee92add Binary files /dev/null and b/public/pagefind/fragment/en_4da380c.pf_fragment differ diff --git a/public/pagefind/fragment/en_4e7e586.pf_fragment b/public/pagefind/fragment/en_4e7e586.pf_fragment new file mode 100755 index 0000000..5fea2d1 Binary files /dev/null and b/public/pagefind/fragment/en_4e7e586.pf_fragment differ diff --git a/public/pagefind/fragment/en_5f39459.pf_fragment b/public/pagefind/fragment/en_5f39459.pf_fragment new file mode 100755 index 0000000..6f9485d Binary files /dev/null and b/public/pagefind/fragment/en_5f39459.pf_fragment differ diff --git a/public/pagefind/fragment/en_63abda6.pf_fragment b/public/pagefind/fragment/en_63abda6.pf_fragment new file mode 100755 index 0000000..ddddc1b Binary files /dev/null and b/public/pagefind/fragment/en_63abda6.pf_fragment differ diff --git a/public/pagefind/fragment/en_646ff16.pf_fragment b/public/pagefind/fragment/en_646ff16.pf_fragment new file mode 100755 index 0000000..21567d6 Binary files /dev/null and b/public/pagefind/fragment/en_646ff16.pf_fragment differ diff --git a/public/pagefind/fragment/en_66fe9a8.pf_fragment b/public/pagefind/fragment/en_66fe9a8.pf_fragment new file mode 100755 index 0000000..69fae3d Binary files /dev/null and b/public/pagefind/fragment/en_66fe9a8.pf_fragment differ diff --git a/public/pagefind/fragment/en_71be6fe.pf_fragment b/public/pagefind/fragment/en_71be6fe.pf_fragment new file mode 100755 index 0000000..34f656b Binary files /dev/null and b/public/pagefind/fragment/en_71be6fe.pf_fragment differ diff --git a/public/pagefind/fragment/en_742c40a.pf_fragment b/public/pagefind/fragment/en_742c40a.pf_fragment new file mode 100755 index 0000000..311bfab Binary files /dev/null and b/public/pagefind/fragment/en_742c40a.pf_fragment differ diff --git a/public/pagefind/fragment/en_7970b4a.pf_fragment b/public/pagefind/fragment/en_7970b4a.pf_fragment new file mode 100755 index 0000000..edfbcd0 Binary files /dev/null and b/public/pagefind/fragment/en_7970b4a.pf_fragment differ diff --git a/public/pagefind/fragment/en_7b25d79.pf_fragment b/public/pagefind/fragment/en_7b25d79.pf_fragment new file mode 100755 index 0000000..31c1e89 Binary files /dev/null and b/public/pagefind/fragment/en_7b25d79.pf_fragment differ diff --git a/public/pagefind/fragment/en_858cf81.pf_fragment b/public/pagefind/fragment/en_858cf81.pf_fragment new file mode 100755 index 0000000..578f270 Binary files /dev/null and b/public/pagefind/fragment/en_858cf81.pf_fragment differ diff --git a/public/pagefind/fragment/en_86893f1.pf_fragment b/public/pagefind/fragment/en_86893f1.pf_fragment new file mode 100755 index 0000000..6101ab0 Binary files /dev/null and b/public/pagefind/fragment/en_86893f1.pf_fragment differ diff --git a/public/pagefind/fragment/en_93fef8e.pf_fragment b/public/pagefind/fragment/en_93fef8e.pf_fragment new file mode 100755 index 0000000..bebf359 Binary files /dev/null and b/public/pagefind/fragment/en_93fef8e.pf_fragment differ diff --git a/public/pagefind/fragment/en_9d821b3.pf_fragment b/public/pagefind/fragment/en_9d821b3.pf_fragment new file mode 100755 index 0000000..16d13fe Binary files /dev/null and b/public/pagefind/fragment/en_9d821b3.pf_fragment differ diff --git a/public/pagefind/fragment/en_a9ec8a2.pf_fragment b/public/pagefind/fragment/en_a9ec8a2.pf_fragment new file mode 100755 index 0000000..2f734bd Binary files /dev/null and b/public/pagefind/fragment/en_a9ec8a2.pf_fragment differ diff --git a/public/pagefind/fragment/en_aa6d5f7.pf_fragment b/public/pagefind/fragment/en_aa6d5f7.pf_fragment new file mode 100755 index 0000000..e16242b Binary files /dev/null and b/public/pagefind/fragment/en_aa6d5f7.pf_fragment differ diff --git a/public/pagefind/fragment/en_c0351fb.pf_fragment b/public/pagefind/fragment/en_c0351fb.pf_fragment new file mode 100755 index 0000000..84ed9c1 Binary files /dev/null and b/public/pagefind/fragment/en_c0351fb.pf_fragment differ diff --git a/public/pagefind/fragment/en_d921aa7.pf_fragment b/public/pagefind/fragment/en_d921aa7.pf_fragment new file mode 100755 index 0000000..426665d Binary files /dev/null and b/public/pagefind/fragment/en_d921aa7.pf_fragment differ diff --git a/public/pagefind/fragment/en_d982f5c.pf_fragment b/public/pagefind/fragment/en_d982f5c.pf_fragment new file mode 100755 index 0000000..35d589f Binary files /dev/null and b/public/pagefind/fragment/en_d982f5c.pf_fragment differ diff --git a/public/pagefind/fragment/en_e446449.pf_fragment b/public/pagefind/fragment/en_e446449.pf_fragment new file mode 100755 index 0000000..ee39c81 Binary files /dev/null and b/public/pagefind/fragment/en_e446449.pf_fragment differ diff --git a/public/pagefind/fragment/en_e6e4d3c.pf_fragment b/public/pagefind/fragment/en_e6e4d3c.pf_fragment new file mode 100755 index 0000000..c13dc28 Binary files /dev/null and b/public/pagefind/fragment/en_e6e4d3c.pf_fragment differ diff --git a/public/pagefind/fragment/en_e6e6b15.pf_fragment b/public/pagefind/fragment/en_e6e6b15.pf_fragment new file mode 100755 index 0000000..854cbf4 Binary files /dev/null and b/public/pagefind/fragment/en_e6e6b15.pf_fragment differ diff --git a/public/pagefind/fragment/en_f29f96a.pf_fragment b/public/pagefind/fragment/en_f29f96a.pf_fragment new file mode 100755 index 0000000..79084b4 Binary files /dev/null and b/public/pagefind/fragment/en_f29f96a.pf_fragment differ diff --git a/public/pagefind/fragment/en_f5a9b3b.pf_fragment b/public/pagefind/fragment/en_f5a9b3b.pf_fragment new file mode 100755 index 0000000..c2906e6 Binary files /dev/null and b/public/pagefind/fragment/en_f5a9b3b.pf_fragment differ diff --git a/public/pagefind/fragment/en_f7921a9.pf_fragment b/public/pagefind/fragment/en_f7921a9.pf_fragment new file mode 100755 index 0000000..7fb93c1 Binary files /dev/null and b/public/pagefind/fragment/en_f7921a9.pf_fragment differ diff --git a/public/pagefind/index/de-de_3ee7bac.pf_index b/public/pagefind/index/de-de_3ee7bac.pf_index new file mode 100755 index 0000000..27ae309 Binary files /dev/null and b/public/pagefind/index/de-de_3ee7bac.pf_index differ diff --git a/public/pagefind/index/en-us_3b1e2cc.pf_index b/public/pagefind/index/en-us_3b1e2cc.pf_index new file mode 100755 index 0000000..0f0427f Binary files /dev/null and b/public/pagefind/index/en-us_3b1e2cc.pf_index differ diff --git a/public/pagefind/index/en-us_7ceca81.pf_index b/public/pagefind/index/en-us_7ceca81.pf_index new file mode 100755 index 0000000..c331cfa Binary files /dev/null and b/public/pagefind/index/en-us_7ceca81.pf_index differ diff --git a/public/pagefind/index/en-us_807ec3b.pf_index b/public/pagefind/index/en-us_807ec3b.pf_index new file mode 100755 index 0000000..a530ace Binary files /dev/null and b/public/pagefind/index/en-us_807ec3b.pf_index differ diff --git a/public/pagefind/index/en-us_d16c8cf.pf_index b/public/pagefind/index/en-us_d16c8cf.pf_index new file mode 100755 index 0000000..21e5ff5 Binary files /dev/null and b/public/pagefind/index/en-us_d16c8cf.pf_index differ diff --git a/public/pagefind/index/en_16b44d7.pf_index b/public/pagefind/index/en_16b44d7.pf_index new file mode 100755 index 0000000..0b33c12 Binary files /dev/null and b/public/pagefind/index/en_16b44d7.pf_index differ diff --git a/public/pagefind/index/en_179b752.pf_index b/public/pagefind/index/en_179b752.pf_index new file mode 100755 index 0000000..919596e Binary files /dev/null and b/public/pagefind/index/en_179b752.pf_index differ diff --git a/public/pagefind/index/en_24f5523.pf_index b/public/pagefind/index/en_24f5523.pf_index new file mode 100755 index 0000000..7acb8d3 Binary files /dev/null and b/public/pagefind/index/en_24f5523.pf_index differ diff --git a/public/pagefind/index/en_63f9ba1.pf_index b/public/pagefind/index/en_63f9ba1.pf_index new file mode 100755 index 0000000..dc13c57 Binary files /dev/null and b/public/pagefind/index/en_63f9ba1.pf_index differ diff --git a/public/pagefind/index/en_7577d54.pf_index b/public/pagefind/index/en_7577d54.pf_index new file mode 100755 index 0000000..a4b65a7 Binary files /dev/null and b/public/pagefind/index/en_7577d54.pf_index differ diff --git a/public/pagefind/index/en_95cad7b.pf_index b/public/pagefind/index/en_95cad7b.pf_index new file mode 100755 index 0000000..16c993b Binary files /dev/null and b/public/pagefind/index/en_95cad7b.pf_index differ diff --git a/public/pagefind/index/en_9af5ffd.pf_index b/public/pagefind/index/en_9af5ffd.pf_index new file mode 100755 index 0000000..8ec3499 Binary files /dev/null and b/public/pagefind/index/en_9af5ffd.pf_index differ diff --git a/public/pagefind/index/en_a13cae5.pf_index b/public/pagefind/index/en_a13cae5.pf_index new file mode 100755 index 0000000..54197d1 Binary files /dev/null and b/public/pagefind/index/en_a13cae5.pf_index differ diff --git a/public/pagefind/index/en_a5a7fc5.pf_index b/public/pagefind/index/en_a5a7fc5.pf_index new file mode 100755 index 0000000..3b48b3f Binary files /dev/null and b/public/pagefind/index/en_a5a7fc5.pf_index differ diff --git a/public/pagefind/pagefind-entry.json b/public/pagefind/pagefind-entry.json index 9e784ff..e66be00 100755 --- a/public/pagefind/pagefind-entry.json +++ b/public/pagefind/pagefind-entry.json @@ -1 +1 @@ -{"version":"1.3.0","languages":{"en":{"hash":"en_eadd96ff86","wasm":"en","page_count":7}}} \ No newline at end of file +{"version":"1.3.0","languages":{"de-de":{"hash":"de-de_7c2d18caafa78","wasm":"de-de","page_count":11}}} \ No newline at end of file diff --git a/public/pagefind/pagefind.de-de_7c2d18caafa78.pf_meta b/public/pagefind/pagefind.de-de_7c2d18caafa78.pf_meta new file mode 100755 index 0000000..034833e Binary files /dev/null and b/public/pagefind/pagefind.de-de_7c2d18caafa78.pf_meta differ diff --git a/public/pagefind/pagefind.en-us_4e3ccd69c8431.pf_meta b/public/pagefind/pagefind.en-us_4e3ccd69c8431.pf_meta new file mode 100755 index 0000000..754f193 Binary files /dev/null and b/public/pagefind/pagefind.en-us_4e3ccd69c8431.pf_meta differ diff --git a/public/pagefind/pagefind.en-us_5965fab4d73c3.pf_meta b/public/pagefind/pagefind.en-us_5965fab4d73c3.pf_meta new file mode 100755 index 0000000..4b7960c Binary files /dev/null and b/public/pagefind/pagefind.en-us_5965fab4d73c3.pf_meta differ diff --git a/public/pagefind/pagefind.en-us_5e585db276157.pf_meta b/public/pagefind/pagefind.en-us_5e585db276157.pf_meta new file mode 100755 index 0000000..3d9721c Binary files /dev/null and b/public/pagefind/pagefind.en-us_5e585db276157.pf_meta differ diff --git a/public/pagefind/pagefind.en-us_7d158baccc135.pf_meta b/public/pagefind/pagefind.en-us_7d158baccc135.pf_meta new file mode 100755 index 0000000..559f560 Binary files /dev/null and b/public/pagefind/pagefind.en-us_7d158baccc135.pf_meta differ diff --git a/public/pagefind/pagefind.en-us_96d6cf461a50e.pf_meta b/public/pagefind/pagefind.en-us_96d6cf461a50e.pf_meta new file mode 100755 index 0000000..7e1a489 Binary files /dev/null and b/public/pagefind/pagefind.en-us_96d6cf461a50e.pf_meta differ diff --git a/public/pagefind/pagefind.en-us_dfac39436a5c8.pf_meta b/public/pagefind/pagefind.en-us_dfac39436a5c8.pf_meta new file mode 100755 index 0000000..764027e Binary files /dev/null and b/public/pagefind/pagefind.en-us_dfac39436a5c8.pf_meta differ diff --git a/public/pagefind/pagefind.en_2c3df57ea1.pf_meta b/public/pagefind/pagefind.en_2c3df57ea1.pf_meta new file mode 100755 index 0000000..1af13a1 Binary files /dev/null and b/public/pagefind/pagefind.en_2c3df57ea1.pf_meta differ diff --git a/public/pagefind/pagefind.en_38edc73aee.pf_meta b/public/pagefind/pagefind.en_38edc73aee.pf_meta new file mode 100755 index 0000000..3dace7d Binary files /dev/null and b/public/pagefind/pagefind.en_38edc73aee.pf_meta differ diff --git a/public/pagefind/pagefind.en_44cfddeeaa.pf_meta b/public/pagefind/pagefind.en_44cfddeeaa.pf_meta new file mode 100755 index 0000000..058661b Binary files /dev/null and b/public/pagefind/pagefind.en_44cfddeeaa.pf_meta differ diff --git a/public/pagefind/pagefind.en_5db73c45cc.pf_meta b/public/pagefind/pagefind.en_5db73c45cc.pf_meta new file mode 100755 index 0000000..66441a7 Binary files /dev/null and b/public/pagefind/pagefind.en_5db73c45cc.pf_meta differ diff --git a/public/pagefind/pagefind.en_9158c54a6b.pf_meta b/public/pagefind/pagefind.en_9158c54a6b.pf_meta new file mode 100755 index 0000000..fccdb2d Binary files /dev/null and b/public/pagefind/pagefind.en_9158c54a6b.pf_meta differ diff --git a/public/pagefind/pagefind.en_9fc12272c1.pf_meta b/public/pagefind/pagefind.en_9fc12272c1.pf_meta new file mode 100755 index 0000000..1d13686 Binary files /dev/null and b/public/pagefind/pagefind.en_9fc12272c1.pf_meta differ diff --git a/public/pagefind/pagefind.en_d6815f6140.pf_meta b/public/pagefind/pagefind.en_d6815f6140.pf_meta new file mode 100755 index 0000000..1b9f97a Binary files /dev/null and b/public/pagefind/pagefind.en_d6815f6140.pf_meta differ diff --git a/public/pagefind/pagefind.en_de11f42534.pf_meta b/public/pagefind/pagefind.en_de11f42534.pf_meta new file mode 100755 index 0000000..9b85882 Binary files /dev/null and b/public/pagefind/pagefind.en_de11f42534.pf_meta differ diff --git a/public/pagefind/pagefind.en_e37af2a6c6.pf_meta b/public/pagefind/pagefind.en_e37af2a6c6.pf_meta new file mode 100755 index 0000000..68bf3f1 Binary files /dev/null and b/public/pagefind/pagefind.en_e37af2a6c6.pf_meta differ diff --git a/public/pagefind/pagefind.en_f0f46b7bdf.pf_meta b/public/pagefind/pagefind.en_f0f46b7bdf.pf_meta new file mode 100755 index 0000000..96639c9 Binary files /dev/null and b/public/pagefind/pagefind.en_f0f46b7bdf.pf_meta differ diff --git a/public/pagefind/pagefind.en_f2b1812af4.pf_meta b/public/pagefind/pagefind.en_f2b1812af4.pf_meta new file mode 100755 index 0000000..c54abe3 Binary files /dev/null and b/public/pagefind/pagefind.en_f2b1812af4.pf_meta differ diff --git a/public/pagefind/pagefind.en_f517619e2c.pf_meta b/public/pagefind/pagefind.en_f517619e2c.pf_meta new file mode 100755 index 0000000..515635a Binary files /dev/null and b/public/pagefind/pagefind.en_f517619e2c.pf_meta differ diff --git a/public/pagefind/wasm.de-de.pagefind b/public/pagefind/wasm.de-de.pagefind new file mode 100755 index 0000000..a96055e Binary files /dev/null and b/public/pagefind/wasm.de-de.pagefind differ diff --git a/public/pagefind/wasm.en-us.pagefind b/public/pagefind/wasm.en-us.pagefind new file mode 100755 index 0000000..6a6c03e Binary files /dev/null and b/public/pagefind/wasm.en-us.pagefind differ diff --git a/public/posts/blocking-invalid-rcpt-postfix/index.html b/public/posts/blocking-invalid-rcpt-postfix/index.html new file mode 100755 index 0000000..c0f4bb8 --- /dev/null +++ b/public/posts/blocking-invalid-rcpt-postfix/index.html @@ -0,0 +1,619 @@ + + + + + + + + +Blocking Invalid Recipients Before They Reach Your Exchange Server | Demians Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skip to main content + + +
+ +
+
+ + + + + + + + +
+

Blocking Invalid Recipients Before They Reach Your Exchange Server

+ + + + + +
+ +
+

Recently, I had to deal with a serious problem: backscatter.
+One of our mail gateways ended up listed on the backscatter.org blacklist for sending bounce messages to forged senders.

+

After checking the logs, I quickly realized that our system wasn’t actually protected against backscatter attacks, so I had to do something about it.

+

What Even Is Backscatter?

+

Backscatter is unwanted email that your mail server sends after receiving a message, usually in the form of a non-delivery report (NDR) or bounce to a forged sender address.

+

It happens when:

+
    +
  • A spammer sends email with a fake “From” address (often an innocent third party).
  • +
  • Your mail server accepts the message first, but later discovers it’s undeliverable.
  • +
  • Your server sends a bounce to the forged address, hitting an innocent person instead of the spammer.
  • +
+

This makes your server appear to be sending spam, even though you’re just bouncing bad mail.

+

The Problem in Our Setup

+

Our mail gateway mostly relays mail to multiple internal Exchange Server clusters.
+We have a list of valid domains configured, so Postfix will only accept mail for those domains.

+

The problem?
+Postfix didn’t know which individual recipients were valid on the downstream Exchange servers.
+That meant it would happily accept messages for nonexistent users, only to later bounce them, classic backscatter behavior.

+

The Goal

+

We needed to reject mail during the SMTP session, ideally right after the RCPT TO command, if the recipient didn’t exist on the Exchange servers.

+

That way, the sending server would get the rejection immediately, and we would never have to generate a bounce message.

+

Possible Solutions I Considered

+

1. relay_recipient_maps

+

This would require maintaining a full list of valid recipients in a Postfix lookup table.
+It works well, but means writing and maintaining a script to sync the list from Active Directory on a regular basis.

+

For us, this was too much custom scripting, too fragile, and too messy to maintain.

+

2. virtual_mailbox_maps with LDAP

+

Another approach would be using LDAP lookups directly against Active Directory to verify recipients in real-time.

+

This can work in some setups, but:

+
    +
  • It adds complexity and dependencies.
  • +
  • It can introduce security concerns.
  • +
  • It didn’t fit well with our environment.
  • +
+

So I ruled it out.

+

The Solution: reject_unverified_recipient

+

While reading through the Postfix Address Verification Howto, I came across the reject_unverified_recipient option - bling! - exactly what I needed.

+

How It Works

+

When an incoming SMTP session reaches the RCPT TO stage:

+
    +
  1. Postfix checks if reject_unverified_recipient is enabled for the recipient domain.
  2. +
  3. If yes, it temporarily probes the downstream mail system to see if the recipient address exists.
  4. +
  5. If the downstream system says: +
      +
    • User exists → Postfix continues processing.
    • +
    • User does not exist → Postfix rejects immediately with: +
      550 5.1.1 <user@example.de>: Recipient address rejected: User unknown
      +
    • +
    +
  6. +
+

Because the rejection happens during SMTP, no bounce is generated, and backscatter is avoided entirely.

+

Implementation in ISPConfig

+

In our case, the mail gateways run Postfix with ISPConfig as the management interface.
+I implemented a new configuration option in ISPConfig for per-domain control of reject_unverified_recipient, along with a validation server to specify for the downstream validation server.

+

Specifying a Validation Server for Recipient Verification

+

One important detail is that Exchange servers cannot validate recipients over the default SMTP transport on port 25. To enable recipient validation, you need to activate it on the Exchange server and use the Hub Transport service, which by default runs on port 2525.

+

Make sure to restrict access to this port, as it requires anonymous login specifically for recipient validation, you don’t want that exposed broadly.

+ + + +
+

Note: This setup does not affect your regular mail flow. Only the SMTP probes used for verifying recipients are sent to this validation server.
+Postfix enables this behavior with the address_verify_transport_maps option.

+ +
+ +

To optimize performance and reduce unnecessary verification probes for addresses that have already been checked, I configured a local cache on the mail gateway using the address_verify_map option. This way, repeated probes for the same recipient are avoided.

+

Now, for domains that need recipient verification, we can enable it in the panel and point Postfix to the appropriate validation transport.

+

The Result

+

After enabling reject_unverified_recipient and pointing it at the Exchange clusters, the backscatter stopped completely.
+We were no longer accepting messages for invalid recipients.

+

Conclusion

+

If your Postfix server is acting as a relay for Exchange (or any downstream mail system) and you’re struggling with backscatter spam, enabling reject_unverified_recipient can be a clean and effective fix.

+

It avoids maintaining large static recipient maps, works dynamically, and ensures that invalid mail is rejected before it ever gets into your system.

+ +
+ + + + + +
+ + + + + +
+ +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/posts/dovecot-index-cache-issues/index.html b/public/posts/dovecot-index-cache-issues/index.html new file mode 100755 index 0000000..9a29b4a --- /dev/null +++ b/public/posts/dovecot-index-cache-issues/index.html @@ -0,0 +1,569 @@ + + + + + + + + +Dovecot Index Cache Issues | Demians Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skip to main content + + +
+ +
+
+ + + + + + + + +
+

Dovecot Index Cache Issues

+ + + + + +
+ +
+

Understanding dovecot.index.cache

+

I recently migrated an old mail server system into its new home. After the migration, I checked the logs and noticed some warnings that looked like this:

+
May 17 11:23:13 server1 dovecot: dsync-local(user@domain.tld)<cRjZCwGnKWiIvicA2dm5Tw>: Error: Mailbox INBOX: mmap(size=511310568) failed with file /var/vmail/domain.tld/user/Maildir/dovecot.index.cache: Cannot allocate memory
+

The error indicates that the dovecot.index.cache file is too big to process, and Dovecot cannot allocate enough memory to handle it.

+

What Are dovecot.index.cache Files?

+

Dovecot, the most popular IMAP server, uses a set of index files (dovecot.index, dovecot.index.cache, dovecot.index.log, etc.) to speed up mailbox access. The file I had to deal with stores cached message metadata (headers, flags, and preview text) so Dovecot does not have to read each message file in the mailbox every time.

+

Over time, the dovecot.index.cache file can grow very large or become outdated. Here are a few reasons why this happens:

+

During Normal Operation

+
    +
  • Deleted or moved messages may leave behind unused metadata.
  • +
  • Corrupt or unreferenced entries might accumulate if a process is interrupted.
  • +
  • Stale data can hang around for years if not explicitly purged.
  • +
+

After a Migration

+

Migrations are particularly prone to creating out-of-sync or bloated index files because:

+
    +
  • File timestamps and UIDs change: Dovecot’s cache is based on assumptions about message state. A migration (e.g. via rsync or imapsync) may change those assumptions, causing the cache to mismatch the actual message data.
  • +
  • Index format/version mismatch: If you switch Dovecot versions between servers, the format of .index.cache might be incompatible or inefficient.
  • +
  • Partial cache rebuilds: After a migration, Dovecot may try to reuse old cache files that no longer reflect the real contents of the mailbox, leading to odd behavior or performance issues.
  • +
+

How to Deal with These Files

+

After migrating to the new servers, I encountered a few of these messages in the log, so I had to search through and find all users affected by this. Since these large files not only consume unnecessary disk space but also affect performance and cause dsync issues, I had to do something about it.

+

Is It Safe to Delete These Files?

+

Short answer: Yes — it’s safe to delete all dovecot.index* files inside a user’s Maildir.

+

Dovecot will automatically regenerate the indexes when needed. This will result in a small delay the first time a user accesses their mailbox after deleting these files, but performance will return to normal quickly.

+

How to Find and Clean Up Large Index Files

+

First, you should check what “large” means for your setup. I checked for files that are larger than 100 MB in size using the following command:

+
find /var/vmail -type f -name "dovecot.index.cache" -size +100M -exec ls -lh {} \;
+

After validating these files and users, I then moved into the user’s Maildir and removed the files:

+
cd /var/vmail/domain.tld/affecteduser/Maildir/
+rm dovecot.index*
+

What Else Can I Do?

+

There’s another way to deal with large index files: you could increase the virtual memory limit that Dovecot is allowed to use, which is controlled by the default_vsz_limit in Dovecot’s configuration files.

+

Another option to prevent future bloat of these files would be adjusting the mail_cache_fields and mail_never_cache_fields in the configuration.

+

I decided to delete these files for affected users because these files had grown so large before migrating to the new setup. After regenerating, the files are much smaller, and time will tell if this becomes an issue again.

+

Conclusion

+

If you encounter these warnings in your logs, it’s worth checking the dovecot.index.cache files and dealing with them. Cleaning them up is safe and easy — Dovecot will take care of rebuilding what it needs.

+ +
+ + + + + +
+ + + + + +
+ +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/posts/google-groups-spam/index.html b/public/posts/google-groups-spam/index.html new file mode 100755 index 0000000..4d295e1 --- /dev/null +++ b/public/posts/google-groups-spam/index.html @@ -0,0 +1,618 @@ + + + + + + + + +Google Groups Spam | Demians Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skip to main content + + +
+ +
+
+ + + + + + + + +
+

Google Groups Spam

+ + + + + +
+ +
+

Understanding the Google Groups Spam Problem

+

Over the past year, I’ve noticed a significant increase in spam messages originating from Google Groups. This issue stems from the way Google Groups is designed: It gives spammers an easy way to distribute large volumes of unwanted mail using legitimate Google mail servers, which makes filtering much harder.

+

Why it Happens

+

There are a few fundamental problems with how Google Groups works that make it particularly attractive to spammers:

+
    +
  • +

    No opt-in required: Spammers can freely add any email address to a Google Group without the recipient’s consent.

    +
  • +
  • +

    Unsubscribing is often impossible: Many spam groups are set to private, meaning that victims cannot even access the group page to unsubscribe.

    +
  • +
  • +

    Amplified spam through auto-responders: Some of these groups include automated mail systems (like ticketing or vacation responders). When these systems reply to the initial spam message, the responses are redistributed to all group members multiplying the traffic in unwanted mails.

    +
  • +
+

The Challenge and Approach

+

While analyzing these messages, I discovered that several legitimate organizations also use Google Groups to distribute newsletters or announcements. That means simply blocking all mail coming from Google Groups would cause false positives and disrupt valid communication.

+

To handle this more effectively, I developed a solution that integrates directly with Rspamd, our spam filtering system:

+
    +
  • +

    Custom Lua plugin: Detects messages originating from Google Groups and assigns a custom symbol.

    +
  • +
  • +

    Composite rules: Use this symbol, combined with other spam indicators, to decide whether a message should be classified as spam.

    +
  • +
+

This approach allows us to target the abusive patterns specifically, without penalizing legitimate use of Google Groups.

+

Technical Configuration

+

To tackle the Google Groups spam issue, I built a small custom Lua plugin for Rspamd that detects messages originating from Google Groups and assigns a custom symbol to them. Once tagged, we can use composite rules to decide whether a message should be treated as spam based on additional indicators.

+

Step 1: Create the Custom Lua Plugin

+

Start by creating a new file at /etc/rspamd/plugins.d/kits_header_google_group.lua:

+
rspamd_config:register_symbol{
+    name = "KITS_HEADER_GOOGLE_GROUP",
+    score = 0.1,
+    group = "headers",
+    description = "Message contains X-Google-Group-Id header or List-Unsubscribe header with googlegroups",
+    callback = function(task)
+      -- Check for X-Google-Group-Id header
+      if task:get_header('X-Google-Group-Id') then
+        return true
+      end
+      
+      -- Check for List-Unsubscribe header containing 'googlegroups'
+      local list_unsubscribe = task:get_header('List-Unsubscribe')
+      if list_unsubscribe and string.find(list_unsubscribe:lower(), 'googlegroups') then
+        return true
+      end
+      
+      return false
+    end
+}
+

This plugin checks for either of the following headers:

+
    +
  • X-Google-Group-Id
  • +
  • List-Unsubscribe containing the string googlegroups
  • +
+

If either is present, the message is tagged with the symbol KITS_HEADER_GOOGLE_GROUP.

+

Step 2: Enable the Plugin

+

Next, register a module with the same name by creating an empty configuration file at /etc/rspamd/modules.d/kits_header_google_group.conf:

+
# Empty config to enable lua plugin
+kits_header_google_group { }
+

At this point, every email that originates from a Google Group will be tagged with the symbol KITS_HEADER_GOOGLE_GROUP and adds a score of 0.1.

+

Step 3: Create Composite Rules

+

The next step is to define composite rules that evaluate whether a tagged message is likely to be spam. Create the following entries in /etc/rspamd/override.d/composite.conf:

+
# Google Group origin with bulk or freemail origin
+KITS_GOOGLE_GROUP_BAD {
+  expression = "KITS_HEADER_GOOGLE_GROUP and (DCC_REJECT | FUZZY_BULK | FREEMAIL_FROM)";
+  score = 8.0;
+}
+
+# Google Group origin with bulk and freemail origin
+KITS_GOOGLE_GROUP_WORST {
+  expression = "KITS_HEADER_GOOGLE_GROUP and (DCC_REJECT | FUZZY_BULK ) and FREEMAIL_FROM";
+  score = 20.0;
+}
+

These rules assign higher scores to messages that originate from Google Groups and match known spam indicators such as DCC_REJECT, FUZZY_BULK, or FREEMAIL_FROM.

+ + + +
+

The naming scheme, scores, and logic here are tailored to my environment. Always adjust the scoring and expressions to fit your setup and verify changes before applying them.

+ +
+ +

Conclusion

+

After deploying this configuration, the amount of spam originating from Google Groups dropped noticeably. Legitimate messages from companies still passed through correctly, while unwanted group spam was effectively flagged or quarantined by Rspamd.

+

If you’re running a mail server, fighting spam is one of the more tedious and ongoing challenges. Thankfully, with Rspamd, we have some of the best and most flexible spam-fighting tools available.

+

Battling spam will always be a cat-and-mouse game, and I’m sure spammers will find new and clever ways to distribute unwanted mail sooner rather than later, but Rspamd will be here to help. I hope to share more useful posts in the future about keeping our mail systems clean with Rspamd.

+ +
+ + + + + +
+ + + + + +
+ +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/posts/imgs/email-route.jpg b/public/posts/imgs/email-route.jpg new file mode 100755 index 0000000..16b79c8 Binary files /dev/null and b/public/posts/imgs/email-route.jpg differ diff --git a/public/posts/index.html b/public/posts/index.html index 74dc4a7..c9fce58 100755 --- a/public/posts/index.html +++ b/public/posts/index.html @@ -1,237 +1,901 @@ - - - - - - - -Posts | PyteDev Blog - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - -
- -
-
- - -
-
-

Introduction -

-
-
-

Welcome to My Blog! I’m Demian, a Sysadmin, Email Infrastructure enthusiast, and a passionate Open Source contributor. This is where I’ll be sharing what I know about system administration, managing email infrastructure, and contributing to open source projects. -What to Expect On this blog, I’ll be writing about: -Sysadmin tips and tools: Everything I’ve learned managing servers, networks, and infrastructure. Email Infrastructure: Best practices for setting up, securing, and managing email systems. Open Source: How I contribute to open source projects, and tips for getting started if you want to do the same. This blog is a space to share knowledge, troubleshoot common issues, and explore new tools and techniques. Whether you’re just getting into system administration, looking for email setup guides, or interested in contributing to open source, you’ll find something useful here. -...

-
-
May 11, 2025
- -
-
+Posts | Demians Blog + - © 2025 PyteDev Blog · + + "> - - Powered by - Hugo & - PaperMod - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skip to main content + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ POSTS +

+ +
+ 5 + posts + +
+ + +
+ + +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ + +
+ + +
+
+ +
+ + + + + + + + + +
+ + + + + + + + +
+

+ Spaceship Distrobox +

+ + +

How I Solved the Distrobox Confusion in My Terminal

+

Recently, I found myself struggling with something that seemed like a simple problem: when I was …

+ + + + + + + + +
+
+ + + + + + + + + + +
+ + + + + + + + +
+

+ Google Groups Spam +

+ + +

Understanding the Google Groups Spam Problem

+

Over the past year, I’ve noticed a significant increase in spam messages originating from Google …

+ + + + + + + + +
+
+ + + + + + + + + + +
+ + + + + + + + +
+

+ Blocking Invalid Recipients Before They Reach Your Exchange Server +

+ + +

Recently, I had to deal with a serious problem: backscatter.
+One of our mail gateways ended up listed on the backscatter.org blacklist for sending …

+ + + + + + + + +
+
+ + + + + + + + + + +
+ + + + + + + + +
+

+ Dovecot Index Cache Issues +

+ + +

Understanding dovecot.index.cache

+

I recently migrated an old mail server system into its new home. After the migration, I checked the logs and noticed …

+ + + + + + + + +
+
+ + + + + + + + + + +
+ + + + + + + + +
+

+ Introduction +

+ + +

Welcome to My Blog!

+

I’m Demian, a Sysadmin, Email Infrastructure enthusiast, and a passionate Open Source contributor. This is where I’ll be sharing …

+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/public/posts/index.xml b/public/posts/index.xml index ede9dbf..73ada55 100755 --- a/public/posts/index.xml +++ b/public/posts/index.xml @@ -1,28 +1,47 @@ - + - Posts on PyteDev Blog + Posts on Demians Blog https://pyte.dev/posts/ - Recent content in Posts on PyteDev Blog - Hugo -- 0.147.2 - en-us - Sun, 11 May 2025 20:13:49 +0200 + Recent content in Posts on Demians Blog + Hugo + de-DE + Sun, 04 Jan 2026 20:33:45 +0100 + + Spaceship Distrobox + https://pyte.dev/posts/spaceship-distrobox/ + Sun, 04 Jan 2026 20:33:45 +0100 + https://pyte.dev/posts/spaceship-distrobox/ + <h1 id="how-i-solved-the-distrobox-confusion-in-my-terminal">How I Solved the Distrobox Confusion in My Terminal</h1> <p>Recently, I found myself struggling with something that seemed like a simple problem: when I was working with multiple terminal windows, I could never remember which one was inside a Distrobox and which one was on my local system. If you&rsquo;ve ever juggled between different environments, you know exactly how annoying this can get.</p> <p>So, I decided to create a quick plugin for my favorite terminal prompt, <strong>Spaceship</strong>, that would clearly indicate when I&rsquo;m inside a Distrobox container. The result is a simple, customizable section that appears right in your prompt, showing the name of the active container. This way, I never have to guess or dig deeper to figure out where I&rsquo;m working.</p> + + + Google Groups Spam + https://pyte.dev/posts/google-groups-spam/ + Fri, 10 Oct 2025 09:26:56 +0200 + https://pyte.dev/posts/google-groups-spam/ + <h1 id="understanding-the-google-groups-spam-problem">Understanding the Google Groups Spam Problem</h1> <p>Over the past year, I&rsquo;ve noticed a significant increase in spam messages originating from Google Groups. This issue stems from the way Google Groups is designed: It gives spammers an easy way to distribute large volumes of unwanted mail using legitimate Google mail servers, which makes filtering much harder.</p> <h2 id="why-it-happens">Why it Happens</h2> <p>There are a few fundamental problems with how Google Groups works that make it particularly attractive to spammers:</p> + + + Blocking Invalid Recipients Before They Reach Your Exchange Server + https://pyte.dev/posts/blocking-invalid-rcpt-postfix/ + Fri, 01 Aug 2025 10:03:15 +0200 + https://pyte.dev/posts/blocking-invalid-rcpt-postfix/ + <p>Recently, I had to deal with a serious problem: <strong>backscatter</strong>.<br> One of our mail gateways ended up listed on the <strong>backscatter.org</strong> blacklist for sending bounce messages to forged senders.</p> <p>After checking the logs, I quickly realized that our system wasn&rsquo;t actually protected against backscatter attacks, so I had to do something about it.</p> <h2 id="what-even-is-backscatter">What Even Is Backscatter?</h2> <p>Backscatter is unwanted email that your mail server sends <strong>after</strong> receiving a message, usually in the form of a <strong>non-delivery report (NDR)</strong> or <strong>bounce</strong> to a <strong>forged sender address</strong>.</p> + + + Dovecot Index Cache Issues + https://pyte.dev/posts/dovecot-index-cache-issues/ + Sun, 18 May 2025 11:34:09 +0200 + https://pyte.dev/posts/dovecot-index-cache-issues/ + <h1 id="understanding-dovecotindexcache">Understanding <code>dovecot.index.cache</code></h1> <p>I recently migrated an old mail server system into its new home. After the migration, I checked the logs and noticed some warnings that looked like this:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="n">May</span> <span class="mi">17</span> <span class="mi">11</span><span class="p">:</span><span class="mi">23</span><span class="p">:</span><span class="mi">13</span> <span class="n">server1</span> <span class="n">dovecot</span><span class="p">:</span> <span class="n">dsync</span><span class="o">-</span><span class="n">local</span><span class="p">(</span><span class="n">user</span><span class="err">@</span><span class="n">domain</span><span class="o">.</span><span class="n">tld</span><span class="p">)</span><span class="o">&lt;</span><span class="n">cRjZCwGnKWiIvicA2dm5Tw</span><span class="o">&gt;</span><span class="p">:</span> <span class="n">Error</span><span class="p">:</span> <span class="n">Mailbox</span> <span class="n">INBOX</span><span class="p">:</span> <span class="n">mmap</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="mi">511310568</span><span class="p">)</span> <span class="n">failed</span> <span class="n">with</span> <span class="n">file</span> <span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">vmail</span><span class="o">/</span><span class="n">domain</span><span class="o">.</span><span class="n">tld</span><span class="o">/</span><span class="n">user</span><span class="o">/</span><span class="n">Maildir</span><span class="o">/</span><span class="n">dovecot</span><span class="o">.</span><span class="n">index</span><span class="o">.</span><span class="n">cache</span><span class="p">:</span> <span class="n">Cannot</span> <span class="n">allocate</span> <span class="n">memory</span> </span></span></code></pre></div><p>The error indicates that the <code>dovecot.index.cache</code> file is too big to process, and Dovecot cannot allocate enough memory to handle it.</p> <h1 id="what-are-dovecotindexcache-files">What Are <code>dovecot.index.cache</code> Files?</h1> <p>Dovecot, the most popular IMAP server, uses a set of index files (<code>dovecot.index</code>, <code>dovecot.index.cache</code>, <code>dovecot.index.log</code>, etc.) to speed up mailbox access. The file I had to deal with stores cached message metadata (headers, flags, and preview text) so Dovecot does not have to read each message file in the mailbox every time.</p> + Introduction https://pyte.dev/posts/my-first-post/ Sun, 11 May 2025 20:13:49 +0200 https://pyte.dev/posts/my-first-post/ - <h1 id="welcome-to-my-blog">Welcome to My Blog!</h1> -<p>I’m <strong>Demian</strong>, a <strong>Sysadmin</strong>, <strong>Email Infrastructure enthusiast</strong>, and a passionate <strong>Open Source contributor</strong>. This is where I’ll be sharing what I know about system administration, managing email infrastructure, and contributing to open source projects.</p> -<h2 id="what-to-expect">What to Expect</h2> -<p>On this blog, I’ll be writing about:</p> -<ul> -<li><strong>Sysadmin tips and tools</strong>: Everything I’ve learned managing servers, networks, and infrastructure.</li> -<li><strong>Email Infrastructure</strong>: Best practices for setting up, securing, and managing email systems.</li> -<li><strong>Open Source</strong>: How I contribute to open source projects, and tips for getting started if you want to do the same.</li> -</ul> -<p>This blog is a space to share knowledge, troubleshoot common issues, and explore new tools and techniques. Whether you’re just getting into system administration, looking for email setup guides, or interested in contributing to open source, you’ll find something useful here.</p> + <h1 id="welcome-to-my-blog">Welcome to My Blog!</h1> <p>I’m <strong>Demian</strong>, a <strong>Sysadmin</strong>, <strong>Email Infrastructure enthusiast</strong>, and a passionate <strong>Open Source contributor</strong>. This is where I’ll be sharing what I know about system administration, managing email infrastructure, and contributing to open source projects.</p> <h2 id="what-to-expect">What to Expect</h2> <p>On this blog, I’ll be writing about:</p> <ul> <li><strong>Sysadmin tips and tools</strong>: Everything I’ve learned managing servers, networks, and infrastructure.</li> <li><strong>Email Infrastructure</strong>: Best practices for setting up, securing, and managing email systems.</li> <li><strong>Open Source</strong>: How I contribute to open source projects, and tips for getting started if you want to do the same.</li> </ul> <p>This blog is a space to share knowledge, troubleshoot common issues, and explore new tools and techniques. Whether you’re just getting into system administration, looking for email setup guides, or interested in contributing to open source, you’ll find something useful here.</p> diff --git a/public/posts/my-first-post/index.html b/public/posts/my-first-post/index.html index af3e320..ae58cd6 100755 --- a/public/posts/my-first-post/index.html +++ b/public/posts/my-first-post/index.html @@ -1,218 +1,325 @@ - - - - - - - -Introduction | PyteDev Blog - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + +Introduction | Demians Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -
- -
-
-
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skip to main content + + +
+ +
+
+ + + + + + + + +
+

Introduction

+ + + +
-

Welcome to My Blog!

+ +
+ +
+

Welcome to My Blog!

I’m Demian, a Sysadmin, Email Infrastructure enthusiast, and a passionate Open Source contributor. This is where I’ll be sharing what I know about system administration, managing email infrastructure, and contributing to open source projects.

-

What to Expect

+

What to Expect

On this blog, I’ll be writing about:

  • Sysadmin tips and tools: Everything I’ve learned managing servers, networks, and infrastructure.
  • @@ -220,92 +327,205 @@ This blog is a space to share knowledge, troubleshoot common issues, and explore
  • Open Source: How I contribute to open source projects, and tips for getting started if you want to do the same.

This blog is a space to share knowledge, troubleshoot common issues, and explore new tools and techniques. Whether you’re just getting into system administration, looking for email setup guides, or interested in contributing to open source, you’ll find something useful here.

-

Why I’m Here

+

Why I’m Here

I’ve been working in IT for a while now, and I’ve learned a lot by trial and error. Writing about my experiences helps me remember the lessons I’ve learned and hopefully helps others along the way. I believe in learning by doing, and this blog is just a reflection of that approach.

I’m excited to share what I’ve picked up, and I hope you’ll find these posts useful, whether you’re a fellow sysadmin or just someone interested in these topics.


-

Let’s Connect

+

Let’s Connect

Feel free to reach out. Thanks for stopping by, and I look forward to sharing more soon!

+
+ + + + + +
+ + + + -
- -
-
+ +